remoteRF-server 0.1.95__tar.gz → 0.1.97__tar.gz
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.
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/PKG-INFO +2 -2
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/README.md +1 -1
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/pyproject.toml +1 -1
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/device_manager.py +286 -100
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/grpc_server.py +4 -4
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/PKG-INFO +2 -2
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/setup.cfg +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/process_arg.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/reservation.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/rpc_manager.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/user_group_cli.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/user_group_handler.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/serverrf_cli.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/tools/__init__.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoteRF-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.97
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
Requires-Dist: grpcio
|
|
6
6
|
Requires-Dist: protobuf
|
|
@@ -350,7 +350,7 @@ todo:
|
|
|
350
350
|
|
|
351
351
|
<!-- Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.) -->
|
|
352
352
|
|
|
353
|
-
ServerRF display meta data as part of devices list / reservation
|
|
353
|
+
<!-- ServerRF display meta data as part of devices list / reservation -->
|
|
354
354
|
|
|
355
355
|
+ online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
|
|
356
356
|
|
|
@@ -339,7 +339,7 @@ todo:
|
|
|
339
339
|
|
|
340
340
|
<!-- Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.) -->
|
|
341
341
|
|
|
342
|
-
ServerRF display meta data as part of devices list / reservation
|
|
342
|
+
<!-- ServerRF display meta data as part of devices list / reservation -->
|
|
343
343
|
|
|
344
344
|
+ online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
|
|
345
345
|
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/device_manager.py
RENAMED
|
@@ -2,47 +2,188 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import hashlib
|
|
6
|
-
from typing import Dict, Tuple, Optional, Any
|
|
7
|
-
|
|
8
5
|
import os
|
|
9
6
|
import re
|
|
7
|
+
import time
|
|
8
|
+
import adi
|
|
10
9
|
import subprocess
|
|
11
10
|
import threading
|
|
12
|
-
from dataclasses import dataclass
|
|
11
|
+
from dataclasses import dataclass, field
|
|
13
12
|
from pathlib import Path
|
|
14
|
-
from typing import Dict, Optional,
|
|
13
|
+
from typing import Dict, Tuple, Optional, Any, List, Iterator
|
|
15
14
|
from contextlib import contextmanager
|
|
16
15
|
|
|
17
|
-
import adi
|
|
18
|
-
|
|
19
16
|
from ..common.utils import validate_token # your salt/hash validator
|
|
20
17
|
from ..host import host_tunnel_server as hts # gives access to hts.TUNNEL_REGISTRY
|
|
21
18
|
|
|
22
|
-
import time # add near imports
|
|
23
|
-
|
|
24
19
|
_state_lock = threading.RLock()
|
|
25
20
|
|
|
21
|
+
|
|
22
|
+
# ============================================================
|
|
23
|
+
# Virtual device (server-side proxy for host-managed hardware)
|
|
24
|
+
# ============================================================
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class VirtualDevice:
|
|
28
|
+
"""
|
|
29
|
+
Server-side proxy for a host-managed device.
|
|
30
|
+
|
|
31
|
+
IMPORTANT:
|
|
32
|
+
- This is NOT an adi device.
|
|
33
|
+
- It only carries routing metadata so your pipeline can forward later.
|
|
34
|
+
"""
|
|
35
|
+
gid: int
|
|
36
|
+
host_id: str
|
|
37
|
+
device_id: str # whatever key the host directory uses (stringified)
|
|
38
|
+
label: str = ""
|
|
39
|
+
serial: str = ""
|
|
40
|
+
kind: str = ""
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
return f"<VirtualDevice gid={self.gid} host={self.host_id} device_id={self.device_id}>"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_virtual(dev: object) -> bool:
|
|
47
|
+
return isinstance(dev, VirtualDevice)
|
|
48
|
+
|
|
49
|
+
|
|
26
50
|
@dataclass
|
|
27
51
|
class _DeviceState:
|
|
28
|
-
dev: object
|
|
52
|
+
dev: object | None
|
|
53
|
+
origin: str # "local" or "host"
|
|
54
|
+
online: bool # for host devices; for local can mirror (dev is not None)
|
|
29
55
|
salt: str
|
|
30
56
|
hsh: str
|
|
31
57
|
name: str
|
|
32
58
|
ident: str
|
|
33
59
|
dtype: str
|
|
34
|
-
io_lock: threading.RLock
|
|
60
|
+
io_lock: threading.RLock = field(default_factory=threading.RLock)
|
|
61
|
+
|
|
35
62
|
|
|
36
63
|
# gid -> state
|
|
37
64
|
_devices: Dict[int, _DeviceState] = {}
|
|
38
65
|
|
|
39
|
-
|
|
66
|
+
|
|
67
|
+
def _is_connected(st: _DeviceState) -> bool:
|
|
68
|
+
if st.origin == "local":
|
|
69
|
+
return st.dev is not None
|
|
70
|
+
# host
|
|
71
|
+
return bool(st.online)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ============================================================
|
|
40
75
|
# Host device pulling/grabbing
|
|
41
|
-
#
|
|
76
|
+
# ============================================================
|
|
42
77
|
|
|
43
78
|
def _get_host_registry():
|
|
44
79
|
return hts.get_tunnel_registry()
|
|
45
80
|
|
|
81
|
+
|
|
82
|
+
_host_sync_lock = threading.RLock()
|
|
83
|
+
_host_sync_last_ms = 0
|
|
84
|
+
_HOST_SYNC_TTL_MS = 500 # tune later
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _sync_host_devices(*, force: bool = False) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Pull host directory and upsert host devices into _devices using their GID.
|
|
90
|
+
|
|
91
|
+
- Does NOT touch local devices (origin="local")
|
|
92
|
+
- Preserves salt/hsh/io_lock for existing entries
|
|
93
|
+
- Marks missing host devices offline (keeps their proxy for reservations)
|
|
94
|
+
- Updates devices_info/device_serialization for host devices (best-effort)
|
|
95
|
+
"""
|
|
96
|
+
global _host_sync_last_ms
|
|
97
|
+
|
|
98
|
+
now_ms = int(time.time() * 1000)
|
|
99
|
+
with _host_sync_lock:
|
|
100
|
+
if not force and (now_ms - _host_sync_last_ms) < _HOST_SYNC_TTL_MS:
|
|
101
|
+
return
|
|
102
|
+
_host_sync_last_ms = now_ms
|
|
103
|
+
|
|
104
|
+
reg = _get_host_registry()
|
|
105
|
+
if reg is None:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
# Do registry calls without holding _state_lock
|
|
109
|
+
snap = reg.device_directory_cached(ttl_ms=0) # device_id -> (host_id, info_obj, is_active)
|
|
110
|
+
|
|
111
|
+
updates: Dict[int, Dict[str, object]] = {}
|
|
112
|
+
for device_id, (host_id, info_obj, is_active) in snap.items():
|
|
113
|
+
# You said device_id is assumed to be the GID
|
|
114
|
+
try:
|
|
115
|
+
gid = int(str(device_id))
|
|
116
|
+
except Exception:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
label = str(getattr(info_obj, "label", "") or "").strip()
|
|
120
|
+
serial = str(getattr(info_obj, "serial", "") or "").strip()
|
|
121
|
+
kind = str(getattr(info_obj, "kind", "") or "").strip()
|
|
122
|
+
|
|
123
|
+
updates[gid] = {
|
|
124
|
+
"host_id": str(host_id),
|
|
125
|
+
"device_id": str(device_id),
|
|
126
|
+
"label": label,
|
|
127
|
+
"serial": serial,
|
|
128
|
+
"kind": kind,
|
|
129
|
+
"online": bool(is_active),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
with _state_lock:
|
|
133
|
+
# Upsert / refresh host entries
|
|
134
|
+
for gid, u in updates.items():
|
|
135
|
+
prev = _devices.get(int(gid))
|
|
136
|
+
|
|
137
|
+
# Collision policy: do not overwrite local devices
|
|
138
|
+
if prev is not None and prev.origin == "local":
|
|
139
|
+
# collision shouldn't happen; keep local stable
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
salt = prev.salt if prev else ""
|
|
143
|
+
hsh = prev.hsh if prev else ""
|
|
144
|
+
lock = prev.io_lock if prev else threading.RLock()
|
|
145
|
+
|
|
146
|
+
label = str(u["label"] or "")
|
|
147
|
+
serial = str(u["serial"] or "")
|
|
148
|
+
kind = str(u["kind"] or "")
|
|
149
|
+
host_id = str(u["host_id"] or "")
|
|
150
|
+
device_id = str(u["device_id"] or "")
|
|
151
|
+
|
|
152
|
+
name = label or (prev.name if prev else f"host-device-{gid}")
|
|
153
|
+
dtype = (kind or (prev.dtype if prev else "")).strip().lower()
|
|
154
|
+
ident = serial or (prev.ident if prev else device_id)
|
|
155
|
+
|
|
156
|
+
_devices[int(gid)] = _DeviceState(
|
|
157
|
+
dev=VirtualDevice(
|
|
158
|
+
gid=int(gid),
|
|
159
|
+
host_id=host_id,
|
|
160
|
+
device_id=device_id,
|
|
161
|
+
label=label,
|
|
162
|
+
serial=serial,
|
|
163
|
+
kind=kind,
|
|
164
|
+
),
|
|
165
|
+
origin="host",
|
|
166
|
+
online=bool(u["online"]),
|
|
167
|
+
salt=salt,
|
|
168
|
+
hsh=hsh,
|
|
169
|
+
name=name,
|
|
170
|
+
ident=ident,
|
|
171
|
+
dtype=dtype,
|
|
172
|
+
io_lock=lock,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Keep legacy maps coherent for host devices too (best-effort)
|
|
176
|
+
devices_info[int(gid)] = name
|
|
177
|
+
if ident:
|
|
178
|
+
device_serialization[int(gid)] = ident
|
|
179
|
+
|
|
180
|
+
# Mark host entries not present in current snap as offline (but keep proxy + auth)
|
|
181
|
+
present = set(updates.keys())
|
|
182
|
+
for gid, st in _devices.items():
|
|
183
|
+
if st.origin == "host" and gid not in present:
|
|
184
|
+
st.online = False
|
|
185
|
+
|
|
186
|
+
|
|
46
187
|
def _local_devices_str_snapshot() -> Dict[int, str]:
|
|
47
188
|
"""
|
|
48
189
|
Local devices from devices.env (whether connected or not).
|
|
@@ -50,8 +191,11 @@ def _local_devices_str_snapshot() -> Dict[int, str]:
|
|
|
50
191
|
out: Dict[int, str] = {}
|
|
51
192
|
with _state_lock:
|
|
52
193
|
for gid, st in _devices.items():
|
|
194
|
+
if st.origin != "local":
|
|
195
|
+
continue
|
|
196
|
+
|
|
53
197
|
name = (st.name or f"device-{gid}").strip()
|
|
54
|
-
status = "online" if (st
|
|
198
|
+
status = "online" if _is_connected(st) else "offline"
|
|
55
199
|
|
|
56
200
|
dtype = (st.dtype or "").strip()
|
|
57
201
|
ident = (st.ident or "").strip()
|
|
@@ -62,7 +206,6 @@ def _local_devices_str_snapshot() -> Dict[int, str]:
|
|
|
62
206
|
if ident:
|
|
63
207
|
meta_parts.append(f"ident={ident}")
|
|
64
208
|
|
|
65
|
-
# optional: show reservation state
|
|
66
209
|
if st.salt or st.hsh:
|
|
67
210
|
status = status + ", reserved"
|
|
68
211
|
|
|
@@ -70,6 +213,7 @@ def _local_devices_str_snapshot() -> Dict[int, str]:
|
|
|
70
213
|
return out
|
|
71
214
|
|
|
72
215
|
def _host_devices_str_snapshot() -> Dict[int, str]:
|
|
216
|
+
_sync_host_devices() # ensure host devices are up-to-date before snapshot
|
|
73
217
|
reg = _get_host_registry()
|
|
74
218
|
if reg is None:
|
|
75
219
|
return {}
|
|
@@ -117,34 +261,36 @@ def _host_devices_str_snapshot() -> Dict[int, str]:
|
|
|
117
261
|
|
|
118
262
|
out[gid] = (
|
|
119
263
|
f"{name} "
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
f"({status})"
|
|
264
|
+
f"[host={host_id}, local_id={local_id}"
|
|
265
|
+
f"{', kind=' + kind if kind else ''}"
|
|
266
|
+
f"{', serial=' + serial if serial else ''}"
|
|
267
|
+
f"{last_seen}"
|
|
268
|
+
f"] ({status})"
|
|
127
269
|
)
|
|
128
270
|
|
|
129
271
|
return out
|
|
130
272
|
|
|
131
|
-
|
|
273
|
+
|
|
274
|
+
# ============================================================
|
|
132
275
|
# Config paths (server-side)
|
|
133
|
-
#
|
|
276
|
+
# ============================================================
|
|
134
277
|
|
|
135
278
|
def _xdg_config_home() -> Path:
|
|
136
279
|
return Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
|
|
137
280
|
|
|
281
|
+
|
|
138
282
|
def _cfg_dir() -> Path:
|
|
139
283
|
# default: ~/.config/remoterf
|
|
140
284
|
return Path(os.getenv("REMOTERF_CONFIG_DIR", _xdg_config_home() / "remoterf"))
|
|
141
285
|
|
|
286
|
+
|
|
142
287
|
def _devices_env_path() -> Path:
|
|
143
288
|
return _cfg_dir() / "devices.env"
|
|
144
289
|
|
|
145
|
-
|
|
290
|
+
|
|
291
|
+
# ============================================================
|
|
146
292
|
# Pluto helpers
|
|
147
|
-
#
|
|
293
|
+
# ============================================================
|
|
148
294
|
|
|
149
295
|
def connect_pluto(*, ip: str = "", usb: str = ""):
|
|
150
296
|
try:
|
|
@@ -158,7 +304,8 @@ def connect_pluto(*, ip: str = "", usb: str = ""):
|
|
|
158
304
|
except Exception as e:
|
|
159
305
|
print(f"Pluto {ip}: {e}")
|
|
160
306
|
return None
|
|
161
|
-
|
|
307
|
+
|
|
308
|
+
|
|
162
309
|
def get_usb_port_from_serial(serial: str) -> str | None:
|
|
163
310
|
serial = (serial or "").strip()
|
|
164
311
|
if not serial:
|
|
@@ -170,7 +317,6 @@ def get_usb_port_from_serial(serial: str) -> str | None:
|
|
|
170
317
|
print(f"Error running iio_info: {e}")
|
|
171
318
|
return None
|
|
172
319
|
|
|
173
|
-
# match either serial= or hw_serial= and extract usb context anywhere on the line
|
|
174
320
|
for line in out.splitlines():
|
|
175
321
|
if (f"serial={serial}" in line) or (f"hw_serial={serial}" in line):
|
|
176
322
|
m = re.search(r"\[usb:([^\]]+)\]", line)
|
|
@@ -181,19 +327,21 @@ def get_usb_port_from_serial(serial: str) -> str | None:
|
|
|
181
327
|
return None
|
|
182
328
|
|
|
183
329
|
|
|
184
|
-
#
|
|
330
|
+
# ============================================================
|
|
185
331
|
# Parse devices.env
|
|
186
|
-
#
|
|
332
|
+
# ============================================================
|
|
187
333
|
|
|
188
334
|
_ENV_LINE = re.compile(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$")
|
|
189
335
|
_DEVICE_KEY_RE = re.compile(r"^DEVICE_(\d+)_(.+)$", re.IGNORECASE)
|
|
190
336
|
|
|
337
|
+
|
|
191
338
|
def _strip_quotes(v: str) -> str:
|
|
192
339
|
v = (v or "").strip()
|
|
193
340
|
if len(v) >= 2 and ((v[0] == v[-1] == '"') or (v[0] == v[-1] == "'")):
|
|
194
341
|
return v[1:-1]
|
|
195
342
|
return v
|
|
196
343
|
|
|
344
|
+
|
|
197
345
|
def _read_env_file(path: Path) -> Dict[str, str]:
|
|
198
346
|
out: Dict[str, str] = {}
|
|
199
347
|
if not path.exists():
|
|
@@ -215,6 +363,7 @@ def _read_env_file(path: Path) -> Dict[str, str]:
|
|
|
215
363
|
out[k] = v
|
|
216
364
|
return out
|
|
217
365
|
|
|
366
|
+
|
|
218
367
|
def _load_device_records() -> Dict[int, Dict[str, str]]:
|
|
219
368
|
"""
|
|
220
369
|
Returns:
|
|
@@ -253,6 +402,7 @@ def _load_device_records() -> Dict[int, Dict[str, str]]:
|
|
|
253
402
|
|
|
254
403
|
return recs
|
|
255
404
|
|
|
405
|
+
|
|
256
406
|
def _connect_from_record(rec: Dict[str, str]):
|
|
257
407
|
"""
|
|
258
408
|
Returns (device_obj, ident, dtype).
|
|
@@ -285,9 +435,9 @@ def _connect_from_record(rec: Dict[str, str]):
|
|
|
285
435
|
return (None, ident, dtype)
|
|
286
436
|
|
|
287
437
|
|
|
288
|
-
#
|
|
438
|
+
# ============================================================
|
|
289
439
|
# Thread-safe runtime state
|
|
290
|
-
#
|
|
440
|
+
# ============================================================
|
|
291
441
|
|
|
292
442
|
# legacy maps (kept for your existing server calls/UI)
|
|
293
443
|
devices_info: Dict[int, str] = {}
|
|
@@ -318,6 +468,8 @@ def _init_from_env() -> None:
|
|
|
318
468
|
|
|
319
469
|
tmp_devices[int(gid)] = _DeviceState(
|
|
320
470
|
dev=dev,
|
|
471
|
+
origin="local",
|
|
472
|
+
online=(dev is not None),
|
|
321
473
|
salt="",
|
|
322
474
|
hsh="",
|
|
323
475
|
name=name,
|
|
@@ -337,13 +489,14 @@ def _init_from_env() -> None:
|
|
|
337
489
|
device_serialization.clear()
|
|
338
490
|
device_serialization.update(tmp_ser)
|
|
339
491
|
|
|
492
|
+
|
|
340
493
|
# initialize on import
|
|
341
494
|
_init_from_env()
|
|
342
495
|
|
|
343
496
|
|
|
344
|
-
#
|
|
497
|
+
# ============================================================
|
|
345
498
|
# Legacy reservation helpers (master token parsing)
|
|
346
|
-
#
|
|
499
|
+
# ============================================================
|
|
347
500
|
|
|
348
501
|
def parse_mastertoken(token: str):
|
|
349
502
|
"""
|
|
@@ -366,9 +519,9 @@ def parse_mastertoken(token: str):
|
|
|
366
519
|
return (device_id, force)
|
|
367
520
|
|
|
368
521
|
|
|
369
|
-
#
|
|
522
|
+
# ============================================================
|
|
370
523
|
# Transmitter stubs (legacy)
|
|
371
|
-
#
|
|
524
|
+
# ============================================================
|
|
372
525
|
|
|
373
526
|
_transmitter = None
|
|
374
527
|
|
|
@@ -382,57 +535,52 @@ def get_transmitter_state() -> bool:
|
|
|
382
535
|
return False
|
|
383
536
|
|
|
384
537
|
|
|
385
|
-
#
|
|
538
|
+
# ============================================================
|
|
386
539
|
# Legacy API (state-safe)
|
|
387
|
-
#
|
|
540
|
+
# ============================================================
|
|
388
541
|
|
|
389
542
|
def get_all_devices() -> Dict[int, Tuple[object, str, str]]:
|
|
390
|
-
|
|
543
|
+
_sync_host_devices()
|
|
544
|
+
|
|
391
545
|
with _state_lock:
|
|
392
546
|
out: Dict[int, Tuple[object, str, str]] = {}
|
|
393
547
|
for gid, st in _devices.items():
|
|
394
|
-
if st
|
|
395
|
-
out[gid] = (st.dev, st.salt, st.hsh)
|
|
548
|
+
if _is_connected(st):
|
|
549
|
+
out[gid] = (st.dev, st.salt, st.hsh) # dev may be VirtualDevice for host
|
|
396
550
|
return out
|
|
397
551
|
|
|
552
|
+
|
|
398
553
|
def get_all_devices_str() -> Dict[int, str]:
|
|
399
|
-
|
|
400
|
-
out = _local_devices_str_snapshot()
|
|
554
|
+
_sync_host_devices()
|
|
401
555
|
|
|
402
|
-
|
|
556
|
+
out = _local_devices_str_snapshot()
|
|
403
557
|
host_out = _host_devices_str_snapshot()
|
|
404
558
|
|
|
405
|
-
#
|
|
406
|
-
# try:
|
|
407
|
-
# from ..host import host_tunnel_server as hts # adjust if your canonical import differs
|
|
408
|
-
# reg = hts.get_tunnel_registry(create=False) if hasattr(hts, "get_tunnel_registry") else None
|
|
409
|
-
# print("\n[device_manager] host_out dump")
|
|
410
|
-
# print(f" hts module: {getattr(hts, '__name__', '<?>')}")
|
|
411
|
-
# print(f" registry id: {id(reg) if reg is not None else None}")
|
|
412
|
-
# print(f" host_out count: {len(host_out)}")
|
|
413
|
-
|
|
414
|
-
# if not host_out:
|
|
415
|
-
# print(" (empty)")
|
|
416
|
-
# else:
|
|
417
|
-
# for gid, s in sorted(host_out.items(), key=lambda kv: int(kv[0])):
|
|
418
|
-
# print(f" {gid}: {s}")
|
|
419
|
-
# print()
|
|
420
|
-
# except Exception as e:
|
|
421
|
-
# print(f"[device_manager] host_out dump failed: {e}")
|
|
422
|
-
|
|
423
|
-
# merge with collision handling
|
|
559
|
+
# merge with collision handling (shouldn't happen; keeps UI stable if it does)
|
|
424
560
|
for gid, s in host_out.items():
|
|
425
561
|
if gid in out:
|
|
426
|
-
|
|
427
|
-
alt = -int(gid) - 1 # deterministic "shadow key"
|
|
562
|
+
alt = -int(gid) - 1
|
|
428
563
|
out[alt] = s + " [gid-collision]"
|
|
429
564
|
else:
|
|
430
565
|
out[gid] = s
|
|
431
566
|
|
|
432
567
|
return out
|
|
433
568
|
|
|
569
|
+
|
|
434
570
|
def set_device(device_id: int, salt: str, hash: str):
|
|
435
|
-
#
|
|
571
|
+
# ensure host devices are upserted so reservations can apply to them too
|
|
572
|
+
_sync_host_devices()
|
|
573
|
+
|
|
574
|
+
with _state_lock:
|
|
575
|
+
st = _devices.get(int(device_id))
|
|
576
|
+
|
|
577
|
+
if not st:
|
|
578
|
+
_sync_host_devices(force=True)
|
|
579
|
+
with _state_lock:
|
|
580
|
+
st = _devices.get(int(device_id))
|
|
581
|
+
if not st:
|
|
582
|
+
return
|
|
583
|
+
|
|
436
584
|
with _state_lock:
|
|
437
585
|
st = _devices.get(int(device_id))
|
|
438
586
|
if not st:
|
|
@@ -440,39 +588,48 @@ def set_device(device_id: int, salt: str, hash: str):
|
|
|
440
588
|
st.salt = str(salt or "")
|
|
441
589
|
st.hsh = str(hash or "")
|
|
442
590
|
|
|
591
|
+
|
|
443
592
|
def device_exists(device_id: int) -> bool:
|
|
444
593
|
did = int(device_id)
|
|
445
594
|
|
|
446
|
-
|
|
595
|
+
_sync_host_devices()
|
|
596
|
+
|
|
597
|
+
# local: connected only (unchanged semantics)
|
|
447
598
|
with _state_lock:
|
|
448
599
|
st = _devices.get(did)
|
|
449
|
-
if st and st.dev is not None:
|
|
600
|
+
if st and st.origin == "local" and st.dev is not None:
|
|
450
601
|
return True
|
|
451
602
|
|
|
452
|
-
# host
|
|
603
|
+
# host: existence as known by registry (unchanged)
|
|
453
604
|
reg = _get_host_registry()
|
|
454
605
|
if reg is None:
|
|
455
606
|
return False
|
|
456
607
|
return bool(reg.is_host_device(str(did)))
|
|
457
608
|
|
|
609
|
+
|
|
458
610
|
def device_is_available(device_id: int) -> bool:
|
|
611
|
+
_sync_host_devices()
|
|
612
|
+
|
|
459
613
|
with _state_lock:
|
|
460
614
|
st = _devices.get(int(device_id))
|
|
461
|
-
if not st or st
|
|
615
|
+
if not st or not _is_connected(st):
|
|
462
616
|
return False
|
|
463
617
|
return (st.salt == "") and (st.hsh == "")
|
|
464
618
|
|
|
619
|
+
|
|
465
620
|
def get_device_by_id(device_id: int):
|
|
466
|
-
|
|
621
|
+
_sync_host_devices()
|
|
622
|
+
|
|
467
623
|
with _state_lock:
|
|
468
624
|
st = _devices.get(int(device_id))
|
|
469
625
|
if not st:
|
|
470
626
|
return None
|
|
471
627
|
return (st.dev, st.salt, st.hsh)
|
|
472
628
|
|
|
629
|
+
|
|
473
630
|
def get_device(*, api_token: str):
|
|
474
631
|
"""
|
|
475
|
-
Legacy behavior:
|
|
632
|
+
Legacy behavior preserved:
|
|
476
633
|
- master_token returns first available device object
|
|
477
634
|
- master_token_<id>[_force] selects a specific device
|
|
478
635
|
- otherwise validate against per-device salt/hash (set via reservations)
|
|
@@ -483,17 +640,24 @@ def get_device(*, api_token: str):
|
|
|
483
640
|
if not api_token:
|
|
484
641
|
return None
|
|
485
642
|
|
|
486
|
-
|
|
643
|
+
_sync_host_devices()
|
|
644
|
+
|
|
487
645
|
with _state_lock:
|
|
488
|
-
snapshot: List[Tuple[int, object, str, str]] = [
|
|
489
|
-
(gid, st.dev, st.salt, st.hsh)
|
|
646
|
+
snapshot: List[Tuple[int, object, str, str, str]] = [
|
|
647
|
+
(gid, st.dev, st.salt, st.hsh, st.origin)
|
|
490
648
|
for gid, st in _devices.items()
|
|
491
|
-
if st
|
|
649
|
+
if _is_connected(st)
|
|
492
650
|
]
|
|
493
651
|
|
|
652
|
+
# Preserve "master prefers local" behavior
|
|
494
653
|
if api_token == master_token:
|
|
495
|
-
|
|
496
|
-
|
|
654
|
+
# local first
|
|
655
|
+
for _, dev, salt, hsh, origin in snapshot:
|
|
656
|
+
if origin == "local" and dev is not None and salt == "" and hsh == "":
|
|
657
|
+
return dev
|
|
658
|
+
# then host
|
|
659
|
+
for _, dev, salt, hsh, origin in snapshot:
|
|
660
|
+
if origin == "host" and dev is not None and salt == "" and hsh == "":
|
|
497
661
|
return dev
|
|
498
662
|
return None
|
|
499
663
|
|
|
@@ -502,13 +666,13 @@ def get_device(*, api_token: str):
|
|
|
502
666
|
device_id, force = parsed
|
|
503
667
|
with _state_lock:
|
|
504
668
|
st = _devices.get(int(device_id))
|
|
505
|
-
if not st or st
|
|
669
|
+
if not st or not _is_connected(st):
|
|
506
670
|
return None
|
|
507
671
|
if force:
|
|
508
672
|
return st.dev
|
|
509
673
|
return st.dev if (st.salt == "" and st.hsh == "") else None
|
|
510
674
|
|
|
511
|
-
for _, dev, salt, hsh in snapshot:
|
|
675
|
+
for _, dev, salt, hsh, _origin in snapshot:
|
|
512
676
|
if dev is None:
|
|
513
677
|
continue
|
|
514
678
|
if salt and hsh and validate_token(salt, hsh, api_token):
|
|
@@ -517,47 +681,55 @@ def get_device(*, api_token: str):
|
|
|
517
681
|
return None
|
|
518
682
|
|
|
519
683
|
|
|
520
|
-
#
|
|
521
|
-
#
|
|
522
|
-
#
|
|
684
|
+
# ============================================================
|
|
685
|
+
# Per-device I/O locking (true concurrency support)
|
|
686
|
+
# ============================================================
|
|
523
687
|
|
|
524
688
|
@contextmanager
|
|
525
689
|
def acquire_device(api_token: str) -> Iterator[Tuple[int, object]]:
|
|
526
690
|
"""
|
|
527
691
|
Thread-safe way to use a device:
|
|
528
692
|
with acquire_device(token) as (gid, dev):
|
|
529
|
-
# safe: only one thread at a time per device
|
|
530
693
|
...
|
|
531
694
|
|
|
532
695
|
- Selects the device using the SAME logic as get_device()
|
|
533
696
|
- Then acquires that device's io_lock
|
|
534
697
|
- Yields (gid, dev)
|
|
698
|
+
|
|
699
|
+
NOTE:
|
|
700
|
+
dev can be an adi device (local) OR a VirtualDevice (host).
|
|
535
701
|
"""
|
|
536
702
|
if not api_token:
|
|
537
703
|
raise RuntimeError("missing api_token")
|
|
538
704
|
|
|
539
|
-
|
|
705
|
+
_sync_host_devices()
|
|
706
|
+
|
|
540
707
|
gid: Optional[int] = None
|
|
541
708
|
|
|
542
709
|
if api_token == master_token:
|
|
543
710
|
with _state_lock:
|
|
711
|
+
# prefer local first
|
|
544
712
|
for k, st in _devices.items():
|
|
545
|
-
if st.
|
|
713
|
+
if st.origin == "local" and _is_connected(st) and st.salt == "" and st.hsh == "":
|
|
546
714
|
gid = k
|
|
547
715
|
break
|
|
716
|
+
if gid is None:
|
|
717
|
+
for k, st in _devices.items():
|
|
718
|
+
if st.origin == "host" and _is_connected(st) and st.salt == "" and st.hsh == "":
|
|
719
|
+
gid = k
|
|
720
|
+
break
|
|
548
721
|
else:
|
|
549
722
|
parsed = parse_mastertoken(api_token)
|
|
550
723
|
if parsed:
|
|
551
724
|
cand, force = parsed
|
|
552
725
|
with _state_lock:
|
|
553
726
|
st = _devices.get(int(cand))
|
|
554
|
-
if st and st
|
|
727
|
+
if st and _is_connected(st):
|
|
555
728
|
if force or (st.salt == "" and st.hsh == ""):
|
|
556
729
|
gid = int(cand)
|
|
557
730
|
else:
|
|
558
|
-
# validate token against per-device salt/hash
|
|
559
731
|
with _state_lock:
|
|
560
|
-
snapshot = [(k, st.dev, st.salt, st.hsh) for k, st in _devices.items() if st
|
|
732
|
+
snapshot = [(k, st.dev, st.salt, st.hsh) for k, st in _devices.items() if _is_connected(st)]
|
|
561
733
|
for k, dev, salt, hsh in snapshot:
|
|
562
734
|
if salt and hsh and validate_token(salt, hsh, api_token):
|
|
563
735
|
gid = k
|
|
@@ -566,10 +738,9 @@ def acquire_device(api_token: str) -> Iterator[Tuple[int, object]]:
|
|
|
566
738
|
if gid is None:
|
|
567
739
|
raise RuntimeError("no device available / invalid token")
|
|
568
740
|
|
|
569
|
-
# lock the selected device for I/O
|
|
570
741
|
with _state_lock:
|
|
571
742
|
st = _devices.get(int(gid))
|
|
572
|
-
if not st or st.dev is None:
|
|
743
|
+
if not st or not _is_connected(st) or st.dev is None:
|
|
573
744
|
raise RuntimeError("device disappeared / not connected")
|
|
574
745
|
lock = st.io_lock
|
|
575
746
|
dev = st.dev
|
|
@@ -581,22 +752,25 @@ def acquire_device(api_token: str) -> Iterator[Tuple[int, object]]:
|
|
|
581
752
|
lock.release()
|
|
582
753
|
|
|
583
754
|
|
|
584
|
-
#
|
|
755
|
+
# ============================================================
|
|
585
756
|
# Optional: reload device definitions (atomic swap)
|
|
586
|
-
#
|
|
757
|
+
# ============================================================
|
|
587
758
|
|
|
588
759
|
def reload_devices() -> None:
|
|
589
760
|
"""
|
|
590
|
-
Reload devices.env and reconnect devices.
|
|
591
|
-
Preserves existing salt/hash for devices that remain (same gid)
|
|
761
|
+
Reload devices.env and reconnect local devices.
|
|
762
|
+
Preserves existing salt/hash for devices that remain (same gid),
|
|
763
|
+
and preserves host virtual devices + their auth state.
|
|
592
764
|
"""
|
|
593
765
|
records = _load_device_records()
|
|
594
766
|
|
|
595
|
-
#
|
|
767
|
+
_sync_host_devices() # ensure host entries exist before snapshot
|
|
768
|
+
|
|
596
769
|
with _state_lock:
|
|
597
770
|
prev_auth = {gid: (st.salt, st.hsh) for gid, st in _devices.items()}
|
|
771
|
+
prev_host = {gid: st for gid, st in _devices.items() if st.origin == "host"}
|
|
598
772
|
|
|
599
|
-
|
|
773
|
+
tmp_local: Dict[int, _DeviceState] = {}
|
|
600
774
|
tmp_info: Dict[int, str] = {}
|
|
601
775
|
tmp_ser: Dict[int, str] = {}
|
|
602
776
|
|
|
@@ -611,8 +785,10 @@ def reload_devices() -> None:
|
|
|
611
785
|
tmp_ser[int(gid)] = ident
|
|
612
786
|
|
|
613
787
|
salt, hsh = prev_auth.get(int(gid), ("", ""))
|
|
614
|
-
|
|
788
|
+
tmp_local[int(gid)] = _DeviceState(
|
|
615
789
|
dev=dev,
|
|
790
|
+
origin="local",
|
|
791
|
+
online=(dev is not None),
|
|
616
792
|
salt=salt,
|
|
617
793
|
hsh=hsh,
|
|
618
794
|
name=name,
|
|
@@ -623,7 +799,14 @@ def reload_devices() -> None:
|
|
|
623
799
|
|
|
624
800
|
with _state_lock:
|
|
625
801
|
_devices.clear()
|
|
626
|
-
|
|
802
|
+
|
|
803
|
+
# restore host entries (but do not overwrite locals if collision)
|
|
804
|
+
for gid, st in prev_host.items():
|
|
805
|
+
if gid not in tmp_local:
|
|
806
|
+
_devices[gid] = st
|
|
807
|
+
|
|
808
|
+
# add locals
|
|
809
|
+
_devices.update(tmp_local)
|
|
627
810
|
|
|
628
811
|
devices_info.clear()
|
|
629
812
|
devices_info.update(tmp_info)
|
|
@@ -631,7 +814,10 @@ def reload_devices() -> None:
|
|
|
631
814
|
device_serialization.clear()
|
|
632
815
|
device_serialization.update(tmp_ser)
|
|
633
816
|
|
|
817
|
+
# refresh host online/offline status after reload (best-effort)
|
|
818
|
+
_sync_host_devices(force=True)
|
|
819
|
+
|
|
634
820
|
|
|
635
821
|
def set_pluto(ip: str = "192.168.2.1"):
|
|
636
822
|
# kept for compatibility (no-op)
|
|
637
|
-
pass
|
|
823
|
+
pass
|
|
@@ -391,10 +391,10 @@ def _cmd_hosts_status() -> None:
|
|
|
391
391
|
reg = hts.get_tunnel_registry()
|
|
392
392
|
assert reg is not None
|
|
393
393
|
|
|
394
|
-
print("\n[hosts status] registry debug")
|
|
395
|
-
print(f" hts module: {hts.__name__}")
|
|
396
|
-
reg = hts.get_tunnel_registry(create=False)
|
|
397
|
-
print(f" registry id: {id(reg) if reg else None}\n")
|
|
394
|
+
# print("\n[hosts status] registry debug")
|
|
395
|
+
# print(f" hts module: {hts.__name__}")
|
|
396
|
+
# reg = hts.get_tunnel_registry(create=False)
|
|
397
|
+
# print(f" registry id: {id(reg) if reg else None}\n")
|
|
398
398
|
|
|
399
399
|
hosts = reg.list_hosts() # host_id -> HostStatus
|
|
400
400
|
devices = reg.list_devices() # device_id -> DeviceStatus
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoteRF-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.97
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
Requires-Dist: grpcio
|
|
6
6
|
Requires-Dist: protobuf
|
|
@@ -350,7 +350,7 @@ todo:
|
|
|
350
350
|
|
|
351
351
|
<!-- Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.) -->
|
|
352
352
|
|
|
353
|
-
ServerRF display meta data as part of devices list / reservation
|
|
353
|
+
<!-- ServerRF display meta data as part of devices list / reservation -->
|
|
354
354
|
|
|
355
355
|
+ online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
|
|
356
356
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/__init__.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/grpc_host_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/grpc_pb2.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/__init__.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/ansi_codes.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/api_token.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/db_connection.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/db_location.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/list_string.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/common/utils/process_arg.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/host/host_directory_store.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/host/host_tunnel_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/cert_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/user_group_cli.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server/server/user_group_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.95 → remoterf_server-0.1.97}/src/remoteRF_server.egg-info/top_level.txt
RENAMED
|
File without changes
|