remoteRF-server 0.2.1__tar.gz → 0.2.3__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.
Files changed (49) hide show
  1. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/PKG-INFO +1 -1
  2. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/pyproject.toml +1 -1
  3. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/idl/schema.py +3 -1
  4. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/grpc_server.py +1 -1
  5. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/reservation.py +37 -37
  6. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/tools/gist_status.py +3 -3
  7. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
  8. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/README.md +0 -0
  9. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/setup.cfg +0 -0
  10. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/__init__.py +0 -0
  11. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/__init__.py +0 -0
  12. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  13. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  14. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  15. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  16. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  17. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/idl/__init__.py +0 -0
  18. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  19. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
  20. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/utils/__init__.py +0 -0
  21. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  22. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/utils/api_token.py +0 -0
  23. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  24. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/utils/db_location.py +0 -0
  25. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/utils/list_string.py +0 -0
  26. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  27. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/drivers/__init__.py +0 -0
  28. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  29. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  30. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/host/__init__.py +0 -0
  31. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/host/host_auth_token.py +0 -0
  32. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/host/host_directory_store.py +0 -0
  33. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  34. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/__init__.py +0 -0
  35. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/acc_perms.py +0 -0
  36. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/cert_provider.py +0 -0
  37. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/device_manager.py +0 -0
  38. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/rpc_manager.py +0 -0
  39. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/user_group_cli.py +0 -0
  40. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/server/user_group_handler.py +0 -0
  41. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/serverrf_cli.py +0 -0
  42. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/tools/__init__.py +0 -0
  43. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/tools/gen_certs.py +0 -0
  44. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
  45. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
  46. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
  47. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
  48. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/src/remoteRF_server.egg-info/requires.txt +0 -0
  49. {remoterf_server-0.2.1 → remoterf_server-0.2.3}/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.2.1
3
+ Version: 0.2.3
4
4
  Description-Content-Type: text/markdown
5
5
  Requires-Dist: grpcio
6
6
  Requires-Dist: protobuf
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoteRF-server"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  dependencies = [
9
9
  "grpcio",
10
10
  "protobuf",
@@ -15,7 +15,7 @@ and the server auto-discovers it on startup via load_drivers().
15
15
  The admin's schema file:
16
16
  1. Imports from this module
17
17
  2. Subclasses DeviceSchema
18
- 3. Decorates with @register_driver("pluto")
18
+ 3. Decorates with @idl_register("pluto")
19
19
  4. Implements make_device(**kwargs) — factory from manifest config
20
20
  5. Decorates exposed methods with @idl_expose(kind="get"|"set"|"call")
21
21
 
@@ -96,6 +96,8 @@ def register_driver(device_type: str):
96
96
  return cls
97
97
  return decorator
98
98
 
99
+ idl_register = register_driver
100
+
99
101
  def get_driver_class(device_type: str) -> type:
100
102
  cls = _DRIVER_REGISTRY.get(device_type.strip().lower())
101
103
  if cls is None:
@@ -227,7 +227,7 @@ def serve():
227
227
  from .cert_provider import start_cert_provider
228
228
  cert_server, cert_thread = start_cert_provider(host=local_ip, port=int(cert_port))
229
229
 
230
- start_status_publisher(period_sec=120)
230
+ start_status_publisher()
231
231
 
232
232
  # Add secure port
233
233
  server.add_secure_port(f'{local_ip}:{local_port}', server_credentials)
@@ -277,7 +277,11 @@ class ReservationHandler:
277
277
  def _clean_expired_reservations(self, device_id: int):
278
278
  """Remove expired reservations from the database."""
279
279
  current_time = datetime.now()
280
- self.db.cursor().execute('DELETE FROM Reservations WHERE device_id = ? AND end_time < ?', (device_id, current_time))
280
+ cur = self.db.cursor()
281
+ try:
282
+ cur.execute('DELETE FROM Reservations WHERE device_id = ? AND end_time < ?', (device_id, current_time))
283
+ finally:
284
+ cur.close()
281
285
 
282
286
  @db_connection
283
287
  def get_reserved_device_ids_for_user(self, username: str) -> list[int]:
@@ -737,14 +741,21 @@ from pathlib import Path
737
741
  LOG_PATH = get_db_dir() / "activity_logs.npy"
738
742
  log_lock = threading.Lock()
739
743
 
740
- def _load_logs ():
744
+ def _load_logs():
741
745
  if LOG_PATH.exists():
742
746
  return np.load(LOG_PATH, allow_pickle=True).tolist()
743
747
  return []
744
748
 
745
- def _save_logs (logs):
749
+ def _save_logs(logs):
746
750
  np.save(LOG_PATH, logs, allow_pickle=True)
747
751
 
752
+ def _append_log(entry: dict):
753
+ """Append a single log entry. Holds log_lock only during file I/O, not during the RPC call."""
754
+ with log_lock:
755
+ logs = _load_logs()
756
+ logs.append(entry)
757
+ _save_logs(logs)
758
+
748
759
  # Store a reference to the original handle_call so we can wrap it
749
760
  ReservationHandler._original_handle_call = ReservationHandler.handle_call
750
761
 
@@ -764,44 +775,33 @@ def sanitize_for_logging(obj):
764
775
  else:
765
776
  return repr(obj)
766
777
 
767
- # Store a reference to the original handle_call so we can wrap it
768
- ReservationHandler._original_handle_call = ReservationHandler.handle_call
769
-
770
778
  def handle_call_wrapper(self, *, function_name, args):
771
779
  """
772
780
  Wrapper around the original handle_call.
773
- Ensures thread-safe logging to .npy by acquiring a global lock
774
- and sanitizes the args/result for pickling.
781
+ The RPC call runs WITHOUT holding log_lock so a slow/stalled disk write
782
+ can never block the server. Logging happens after the call returns.
775
783
  """
776
- with log_lock:
777
- # Load logs (thread-safe)
778
- activity_log = _load_logs()
779
-
780
- # Make a sanitized copy of args (to hide passwords and non-pickleable objects)
781
- sanitized_args = sanitize_for_logging(dict(args))
782
- if 'pw' in sanitized_args:
783
- sanitized_args['pw'] = '*'
784
-
785
- # Call the original handle_call
786
- result = self._original_handle_call(function_name=function_name, args=args)
787
-
788
- # Sanitize the result
789
- sanitized_result = sanitize_for_logging(result)
790
-
791
- # Append a new entry to our logs
792
- username = sanitize_for_logging(unmap_arg(args['un'])) if 'un' in args else 'N/A'
793
- activity_log.append({
794
- "timestamp": datetime.now().isoformat(),
795
- "action": function_name,
796
- "username": username,
797
- "details": {
798
- "args": sanitized_args,
799
- "result": sanitized_result
800
- }
801
- })
802
-
803
- # Save logs back to .npy (thread-safe)
804
- _save_logs(activity_log)
784
+ # Make a sanitized copy of args before the call (no lock needed here)
785
+ sanitized_args = sanitize_for_logging(dict(args))
786
+ if 'pw' in sanitized_args:
787
+ sanitized_args['pw'] = '*'
788
+
789
+ # Run the actual RPC — NOT inside log_lock
790
+ result = self._original_handle_call(function_name=function_name, args=args)
791
+
792
+ # Log asynchronously after the call completes
793
+ sanitized_result = sanitize_for_logging(result)
794
+ username = sanitize_for_logging(unmap_arg(args['un'])) if 'un' in args else 'N/A'
795
+ entry = {
796
+ "timestamp": datetime.now().isoformat(),
797
+ "action": function_name,
798
+ "username": username,
799
+ "details": {
800
+ "args": sanitized_args,
801
+ "result": sanitized_result
802
+ }
803
+ }
804
+ threading.Thread(target=_append_log, args=(entry,), daemon=True).start()
805
805
 
806
806
  return result
807
807
 
@@ -116,7 +116,7 @@ _publisher_thread: Optional[threading.Thread] = None
116
116
  _publisher_lock = threading.Lock()
117
117
 
118
118
 
119
- def start_status_publisher(*, period_sec: int = 60) -> None:
119
+ def start_status_publisher() -> None:
120
120
  global _publisher_thread
121
121
  with _publisher_lock:
122
122
  if _publisher_thread is not None and _publisher_thread.is_alive():
@@ -124,13 +124,13 @@ def start_status_publisher(*, period_sec: int = 60) -> None:
124
124
 
125
125
  t = threading.Thread(
126
126
  target=_publisher_loop,
127
- args=(int(period_sec),),
127
+ args=(int(120),),
128
128
  name="gist-status-publisher",
129
129
  daemon=True,
130
130
  )
131
131
  t.start()
132
132
  _publisher_thread = t
133
- print(f"[gist_status] started (period={period_sec}s)", file=sys.stderr)
133
+ print(f"[gist_status] started (period={120}s)", file=sys.stderr)
134
134
 
135
135
 
136
136
  # if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Description-Content-Type: text/markdown
5
5
  Requires-Dist: grpcio
6
6
  Requires-Dist: protobuf