nedo-vision-worker 1.3.7__py3-none-any.whl → 1.3.9__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.
@@ -6,5 +6,5 @@ A library for running worker agents in the Nedo Vision platform.
6
6
 
7
7
  from .worker_service import WorkerService
8
8
 
9
- __version__ = "1.3.7"
9
+ __version__ = "1.3.9"
10
10
  __all__ = ["WorkerService"]
@@ -0,0 +1,51 @@
1
+ import logging
2
+ from nedo_vision_worker.config.ConfigurationManager import ConfigurationManager
3
+ from nedo_vision_worker.database.DatabaseManager import set_storage_path, DatabaseManager
4
+ from nedo_vision_worker.worker_service import WorkerService
5
+
6
+
7
+ def start_worker(
8
+ *,
9
+ server_host: str,
10
+ server_port: int,
11
+ token: str,
12
+ system_usage_interval: int,
13
+ rtmp_server: str,
14
+ storage_path: str,
15
+ log_level: str = "INFO",
16
+ ) -> WorkerService:
17
+ # Logging (only once, force allowed)
18
+ logging.basicConfig(
19
+ level=getattr(logging, log_level),
20
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
21
+ force=True,
22
+ )
23
+
24
+ logger = logging.getLogger("nedo-worker")
25
+
26
+ # Storage & DB
27
+ set_storage_path(storage_path)
28
+ DatabaseManager.init_databases()
29
+
30
+ # Configuration
31
+ configuration_manager = ConfigurationManager(
32
+ worker_token=token,
33
+ server_host=server_host,
34
+ server_port=server_port,
35
+ rtmp_server_url=rtmp_server,
36
+ logger=logging.getLogger("configuration_manager"),
37
+ )
38
+
39
+ service = WorkerService(
40
+ configuration_manager=configuration_manager,
41
+ server_host=server_host,
42
+ token=token,
43
+ system_usage_interval=system_usage_interval,
44
+ rtmp_server=rtmp_server,
45
+ storage_path=storage_path,
46
+ )
47
+
48
+ logger.info("🚀 Starting Nedo Vision Worker Service")
49
+ service.run()
50
+
51
+ return service
nedo_vision_worker/cli.py CHANGED
@@ -1,224 +1,75 @@
1
1
  import argparse
2
2
  import signal
3
3
  import sys
4
- import traceback
5
4
  import logging
6
5
  from typing import NoReturn
7
6
 
8
- from .worker_service import WorkerService
9
-
7
+ from nedo_vision_worker.bootstrap import start_worker
8
+ from nedo_vision_worker import __version__
10
9
 
11
10
  class NedoWorkerCLI:
12
- """Main CLI application for Nedo Vision Worker Service."""
13
-
14
11
  def __init__(self):
15
12
  self.logger = logging.getLogger(__name__)
16
13
  self._setup_signal_handlers()
17
-
14
+ self._service = None
15
+
18
16
  def _setup_signal_handlers(self) -> None:
19
- """Set up signal handlers for graceful shutdown."""
20
17
  signal.signal(signal.SIGINT, self._signal_handler)
21
18
  signal.signal(signal.SIGTERM, self._signal_handler)
22
-
19
+
23
20
  def _signal_handler(self, signum: int, frame) -> NoReturn:
24
- """Handle system signals for graceful shutdown."""
25
- self.logger.info(f"Received signal {signum}, shutting down...")
21
+ self.logger.info("🛑 Shutdown signal received")
22
+ if self._service and hasattr(self._service, "stop"):
23
+ self._service.stop()
26
24
  sys.exit(0)
27
-
25
+
28
26
  def create_parser(self) -> argparse.ArgumentParser:
29
- """Create and configure the argument parser."""
30
- parser = argparse.ArgumentParser(
31
- description="Nedo Vision Worker Service",
32
- formatter_class=argparse.RawDescriptionHelpFormatter,
33
- epilog="""
34
- Examples:
35
- # Check system dependencies and requirements
36
- nedo-worker doctor
37
-
38
- # Start worker service
39
- nedo-worker run --token your-token-here
40
-
41
- # Start with custom configuration
42
- nedo-worker run --token your-token-here \\
43
- --rtmp-server rtmp://custom.server.com:1935/live \\
44
- --server-host custom.server.com \\
45
- --storage-path /custom/storage/path
46
- """
47
- )
48
-
49
- parser.add_argument(
50
- "--version",
51
- action="version",
52
- version="nedo-vision-worker 1.2.1"
53
- )
54
-
55
- subparsers = parser.add_subparsers(
56
- dest='command',
57
- help='Available commands',
58
- required=True
59
- )
60
-
61
- self._add_doctor_command(subparsers)
62
- self._add_run_command(subparsers)
63
-
27
+ parser = argparse.ArgumentParser(description="Nedo Vision Worker Service")
28
+
29
+ parser.add_argument("--version", action="version", version=f"nedo-vision-worker {__version__}")
30
+
31
+ subparsers = parser.add_subparsers(dest="command", required=True)
32
+
33
+ subparsers.add_parser("doctor", help="Check system dependencies")
34
+
35
+ run = subparsers.add_parser("run", help="Start worker")
36
+
37
+ run.add_argument("--token", required=True)
38
+ run.add_argument("--server-host", default="be.vision.sindika.co.id")
39
+ run.add_argument("--server-port", type=int, default=50051)
40
+ run.add_argument("--rtmp-server", default="rtmp://live.vision.sindika.co.id:1935/live")
41
+ run.add_argument("--storage-path", default="data")
42
+ run.add_argument("--system-usage-interval", type=int, default=30)
43
+ run.add_argument("--log-level", default="INFO")
44
+
64
45
  return parser
65
-
66
- def _add_doctor_command(self, subparsers) -> None:
67
- """Add the doctor command."""
68
- subparsers.add_parser(
69
- 'doctor',
70
- help='Check system dependencies and requirements',
71
- description='Run diagnostic checks for FFmpeg, OpenCV, gRPC and other dependencies'
72
- )
73
-
74
- def _add_run_command(self, subparsers) -> None:
75
- """Add the run command with its arguments."""
76
- run_parser = subparsers.add_parser(
77
- 'run',
78
- help='Start the worker service',
79
- description='Start the Nedo Vision Worker Service'
80
- )
81
-
82
- run_parser.add_argument(
83
- "--token",
84
- required=True,
85
- help="Authentication token for the worker (obtained from frontend)"
86
- )
87
-
88
- run_parser.add_argument(
89
- "--server-host",
90
- default="be.vision.sindika.co.id",
91
- help="Server hostname for communication (default: %(default)s)"
92
- )
93
-
94
- run_parser.add_argument(
95
- "--server-port",
96
- type=int,
97
- default=50051,
98
- help="Server port for gRPC communication (default: %(default)s)"
99
- )
100
-
101
- run_parser.add_argument(
102
- "--rtmp-server",
103
- default="rtmp://live.vision.sindika.co.id:1935/live",
104
- help="RTMP server URL for video streaming (default: %(default)s)"
105
- )
106
-
107
- run_parser.add_argument(
108
- "--storage-path",
109
- default="data",
110
- help="Storage path for databases and files (default: %(default)s)"
111
- )
112
-
113
- run_parser.add_argument(
114
- "--system-usage-interval",
115
- type=int,
116
- default=30,
117
- metavar="SECONDS",
118
- help="System usage reporting interval in seconds (default: %(default)s)"
119
- )
120
-
121
- run_parser.add_argument(
122
- "--log-level",
123
- choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
124
- default="INFO",
125
- help="Set the logging level (default: %(default)s)"
126
- )
127
-
128
- def run_doctor_command(self) -> int:
129
- """Execute the doctor command."""
130
- try:
131
- from .doctor import main as doctor_main
46
+
47
+ def run(self) -> int:
48
+ parser = self.create_parser()
49
+ args = parser.parse_args()
50
+
51
+ if args.command == "doctor":
52
+ from nedo_vision_worker.doctor import main as doctor_main
132
53
  return doctor_main()
133
- except ImportError as e:
134
- self.logger.error(f"Failed to import doctor module: {e}")
135
- return 1
136
- except Exception as e:
137
- self.logger.error(f"Doctor command failed: {e}")
138
- return 1
139
-
140
- def run_worker_service(self, args: argparse.Namespace) -> int:
141
- """Start and run the worker service."""
142
- # Configure logging
143
- logging.basicConfig(
144
- level=getattr(logging, args.log_level),
145
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
146
- )
147
-
148
- try:
149
- # Create the worker service
150
- service = WorkerService(
54
+
55
+ if args.command == "run":
56
+ self._service = start_worker(
151
57
  server_host=args.server_host,
58
+ server_port=args.server_port,
152
59
  token=args.token,
153
60
  system_usage_interval=args.system_usage_interval,
154
61
  rtmp_server=args.rtmp_server,
155
62
  storage_path=args.storage_path,
156
- server_port=args.server_port
63
+ log_level=args.log_level,
157
64
  )
158
-
159
- # Log startup information
160
- self._log_startup_info(args)
161
-
162
- # Start the service
163
- service.run()
164
-
165
- # Keep the service running
166
- self._wait_for_service(service)
167
-
168
- return 0
169
-
170
- except KeyboardInterrupt:
171
- self.logger.info("Shutdown requested by user")
172
- return 0
173
- except Exception as e:
174
- self.logger.error(f"Service failed: {e}")
175
- if args.log_level == "DEBUG":
176
- traceback.print_exc()
177
- return 1
178
-
179
- def _log_startup_info(self, args: argparse.Namespace) -> None:
180
- """Log service startup information."""
181
- self.logger.info("🚀 Starting Nedo Vision Worker Service...")
182
- self.logger.info(f"🌐 Server: {args.server_host}")
183
- self.logger.info(f"🔑 Token: {args.token[:8]}{'*' * (len(args.token) - 8)}")
184
- self.logger.info(f"⏱️ System Usage Interval: {args.system_usage_interval}s")
185
- self.logger.info(f"📡 RTMP Server: {args.rtmp_server}")
186
- self.logger.info(f"💾 Storage Path: {args.storage_path}")
187
- self.logger.info("Press Ctrl+C to stop the service")
188
-
189
- def _wait_for_service(self, service: WorkerService) -> None:
190
- """Wait for the service to run and handle shutdown."""
191
- import time
192
-
193
- try:
194
- while getattr(service, 'running', False):
195
- time.sleep(1)
196
- except KeyboardInterrupt:
197
- self.logger.info("🛑 Shutdown requested...")
198
- finally:
199
- if hasattr(service, 'stop'):
200
- service.stop()
201
- self.logger.info("✅ Service stopped successfully")
202
-
203
- def run(self) -> int:
204
- """Main entry point for the CLI application."""
205
- parser = self.create_parser()
206
- args = parser.parse_args()
207
-
208
- if args.command == 'doctor':
209
- return self.run_doctor_command()
210
- elif args.command == 'run':
211
- return self.run_worker_service(args)
212
- else:
213
- parser.print_help()
214
- return 1
65
+ signal.pause() # wait for signal
66
+
67
+ return 0
215
68
 
216
69
 
217
70
  def main() -> int:
218
- """Main CLI entry point."""
219
- cli = NedoWorkerCLI()
220
- return cli.run()
71
+ return NedoWorkerCLI().run()
221
72
 
222
73
 
223
74
  if __name__ == "__main__":
224
- sys.exit(main())
75
+ sys.exit(main())
@@ -1,84 +1,138 @@
1
1
  import logging
2
+ import grpc
3
+ from typing import Optional
2
4
  from ..models.config import ConfigEntity # ORM model for server_config
3
5
  from ..database.DatabaseManager import DatabaseManager # DatabaseManager for managing sessions
4
6
 
7
+ from ..util.HardwareID import HardwareID
8
+ from ..services.ConnectionInfoClient import ConnectionInfoClient
9
+ from .ConfigurationManagerInterface import ConfigurationManagerInterface
5
10
 
6
- class ConfigurationManager:
11
+ class ConfigurationManager(ConfigurationManagerInterface):
7
12
  """
8
- A class to manage server configuration stored in the 'config' database using SQLAlchemy.
13
+ A class to manage local and remote configuration stored in the 'config' database.
9
14
  """
10
15
 
11
- @staticmethod
12
- def init_database():
13
- """
14
- Initialize the 'config' database and create the `server_config` table if it doesn't exist.
15
- """
16
+ def __init__(self, worker_token: str, server_host: str, server_port: int, rtmp_server_url: str, logger: logging.Logger):
16
17
  try:
17
- DatabaseManager.init_databases()
18
+ self._logger = logger
19
+ self._worker_token = worker_token
20
+ self._server_host = server_host
21
+ self._server_port = server_port
22
+ self._rtmp_server_url = rtmp_server_url
23
+
24
+ self._initialize_remote_configuration()
25
+
18
26
  logging.info("✅ [APP] Configuration database initialized successfully.")
19
27
  except Exception as e:
20
28
  logging.exception("❌ [APP] Failed to initialize the configuration database.")
21
29
  raise RuntimeError("Database initialization failed.") from e
22
-
23
- @staticmethod
24
- def is_config_initialized() -> bool:
30
+
31
+ def get_config(self, key: str) -> str:
25
32
  """
26
- Check if the configuration is already initialized.
33
+ Retrieve the value of a specific configuration key from the 'config' database.
34
+
35
+ Args:
36
+ key (str): The configuration key.
27
37
 
28
38
  Returns:
29
- bool: True if configuration exists, False otherwise.
39
+ str: The configuration value, or None if the key does not exist.
30
40
  """
41
+ if not key or not isinstance(key, str):
42
+ raise ValueError("⚠️ The 'key' must be a non-empty string.")
43
+
31
44
  session = None
32
45
  try:
33
46
  session = DatabaseManager.get_session("config")
34
- config_count = session.query(ConfigEntity).count()
35
- return config_count > 0 # True if at least one config exists
47
+ logging.info(f"🔍 [APP] Retrieving configuration key: {key}")
48
+ config = session.query(ConfigEntity).filter_by(key=key).first()
49
+ if config:
50
+ logging.info(f"✅ [APP] Configuration key '{key}' retrieved successfully.")
51
+ return config.value
52
+ else:
53
+ logging.warning(f"⚠️ [APP] Configuration key '{key}' not found.")
54
+ return ""
36
55
  except Exception as e:
37
- logging.exception("❌ [APP] Failed to check if configuration is initialized.")
38
- return False
56
+ logging.exception(f"❌ [APP] Failed to retrieve configuration key '{key}': {e}")
57
+ raise RuntimeError(f"Failed to retrieve configuration key '{key}'") from e
39
58
  finally:
40
59
  if session:
41
60
  session.close()
42
-
43
- @staticmethod
44
- def set_config(key: str, value: str):
61
+
62
+ def get_all_configs(self) -> Optional[dict]:
45
63
  """
46
- Set or update a configuration key-value pair in the 'config' database.
64
+ Retrieve all configuration key-value pairs from the 'config' database.
47
65
 
48
- Args:
49
- key (str): The configuration key.
50
- value (str): The configuration value.
66
+ Returns:
67
+ dict: A dictionary of all configuration key-value pairs.
51
68
  """
52
- if not key or not isinstance(key, str):
53
- raise ValueError("⚠️ [APP] The 'key' must be a non-empty string.")
54
- if not isinstance(value, str):
55
- raise ValueError("⚠️ [APP] The 'value' must be a string.")
56
-
57
69
  session = None
58
70
  try:
59
71
  session = DatabaseManager.get_session("config")
60
- logging.info(f"🔧 Attempting to set configuration: {key} = {value}")
61
- existing_config = session.query(ConfigEntity).filter_by(key=key).first()
62
- if existing_config:
63
- logging.info(f"🔄 [APP] Updating configuration key: {key}")
64
- existing_config.value = value
72
+ logging.info("🔍 [APP] Retrieving all configuration keys.")
73
+ configs = session.query(ConfigEntity).all()
74
+ if configs:
75
+ logging.info(" [APP] All configuration keys retrieved successfully.")
76
+ return {config.key: config.value for config in configs}
65
77
  else:
66
- logging.info(f" [APP] Adding new configuration key: {key}")
67
- new_config = ConfigEntity(key=key, value=value)
68
- session.add(new_config)
69
- session.commit()
70
- logging.info(f"✅ [APP] Configuration key '{key}' set successfully.")
78
+ logging.info("⚠️ [APP] No configuration keys found.")
79
+ return None
71
80
  except Exception as e:
72
- if session:
73
- session.rollback()
74
- logging.exception(f"❌ [APP] Failed to set configuration key '{key}': {e}")
75
- raise RuntimeError(f"Failed to set configuration key '{key}'") from e
81
+ logging.exception("❌ [APP] Failed to retrieve all configuration keys.")
82
+ raise RuntimeError("Failed to retrieve all configuration keys.") from e
76
83
  finally:
77
84
  if session:
78
85
  session.close()
79
86
 
80
- @staticmethod
81
- def set_config_batch(configs: dict):
87
+ def _initialize_remote_configuration(self):
88
+ """
89
+ Initialize the application configuration using the provided token
90
+ and saving configuration data locally.
91
+ """
92
+ try:
93
+ # Get hardware ID
94
+ hardware_id = HardwareID.get_unique_id()
95
+
96
+ self._logger.info(f"🖥️ [APP] Detected Hardware ID: {hardware_id}")
97
+ self._logger.info(f"🌐 [APP] Using Server Host: {self._server_host}")
98
+
99
+ # Check if token is provided
100
+ if not self._worker_token:
101
+ raise ValueError("Token is required for worker initialization. Please provide a token obtained from the frontend.")
102
+
103
+ # Get connection info using the ConnectionInfoClient
104
+ connection_client = ConnectionInfoClient(self._server_host, self._server_port, self._worker_token)
105
+ connection_result = connection_client.get_connection_info()
106
+
107
+ if not connection_result["success"]:
108
+ logging.error(f"Device connection info failed: {connection_result['message']}")
109
+ raise ValueError(f"Initializing remote config failed, reason: {connection_result['message']}")
110
+
111
+ worker_id = connection_result.get('id')
112
+ if not worker_id:
113
+ raise ValueError("No worker_id returned from connection info!")
114
+
115
+ self._set_config_batch({
116
+ "worker_id": worker_id,
117
+ "server_host": self._server_host,
118
+ "rtmp_server": self._rtmp_server_url,
119
+ "server_port": str(self._server_port),
120
+ "token": self._worker_token,
121
+ "rabbitmq_host": connection_result['rabbitmq_host'],
122
+ "rabbitmq_port": str(connection_result['rabbitmq_port']),
123
+ "rabbitmq_username": connection_result['rabbitmq_username'],
124
+ "rabbitmq_password": connection_result['rabbitmq_password']
125
+ })
126
+ self._print_config()
127
+
128
+ except ValueError as ve:
129
+ logging.error(f"Validation error: {ve}")
130
+ except grpc.RpcError as ge:
131
+ logging.error(f"Grpc Error: {ge}")
132
+ except Exception as e:
133
+ logging.error(f"Unexpected error during initialization: {e}")
134
+
135
+ def _set_config_batch(self, configs: dict):
82
136
  """
83
137
  Set or update multiple configuration key-value pairs in the 'config' database in a batch operation.
84
138
 
@@ -116,71 +170,12 @@ class ConfigurationManager:
116
170
  if session:
117
171
  session.close()
118
172
 
119
- @staticmethod
120
- def get_config(key: str) -> str:
121
- """
122
- Retrieve the value of a specific configuration key from the 'config' database.
123
-
124
- Args:
125
- key (str): The configuration key.
126
-
127
- Returns:
128
- str: The configuration value, or None if the key does not exist.
129
- """
130
- if not key or not isinstance(key, str):
131
- raise ValueError("⚠️ The 'key' must be a non-empty string.")
132
-
133
- session = None
134
- try:
135
- session = DatabaseManager.get_session("config")
136
- logging.info(f"🔍 [APP] Retrieving configuration key: {key}")
137
- config = session.query(ConfigEntity).filter_by(key=key).first()
138
- if config:
139
- logging.info(f"✅ [APP] Configuration key '{key}' retrieved successfully.")
140
- return config.value
141
- else:
142
- logging.warning(f"⚠️ [APP] Configuration key '{key}' not found.")
143
- return None
144
- except Exception as e:
145
- logging.exception(f"❌ [APP] Failed to retrieve configuration key '{key}': {e}")
146
- raise RuntimeError(f"Failed to retrieve configuration key '{key}'") from e
147
- finally:
148
- if session:
149
- session.close()
150
-
151
- @staticmethod
152
- def get_all_configs() -> dict:
153
- """
154
- Retrieve all configuration key-value pairs from the 'config' database.
155
-
156
- Returns:
157
- dict: A dictionary of all configuration key-value pairs.
158
- """
159
- session = None
160
- try:
161
- session = DatabaseManager.get_session("config")
162
- logging.info("🔍 [APP] Retrieving all configuration keys.")
163
- configs = session.query(ConfigEntity).all()
164
- if configs:
165
- logging.info("✅ [APP] All configuration keys retrieved successfully.")
166
- return {config.key: config.value for config in configs}
167
- else:
168
- logging.info("⚠️ [APP] No configuration keys found.")
169
- return {}
170
- except Exception as e:
171
- logging.exception("❌ [APP] Failed to retrieve all configuration keys.")
172
- raise RuntimeError("Failed to retrieve all configuration keys.") from e
173
- finally:
174
- if session:
175
- session.close()
176
-
177
- @staticmethod
178
- def print_config():
173
+ def _print_config(self):
179
174
  """
180
175
  Print all configuration key-value pairs to the console.
181
176
  """
182
177
  try:
183
- configs = ConfigurationManager.get_all_configs()
178
+ configs = self.get_all_configs()
184
179
  if configs:
185
180
  print("📄 Current Configuration:")
186
181
  for key, value in configs.items():
@@ -0,0 +1,12 @@
1
+ from typing import Optional
2
+ from abc import ABC, abstractmethod
3
+
4
+
5
+ class ConfigurationManagerInterface(ABC):
6
+ @abstractmethod
7
+ def get_config(self, key: str) -> str:
8
+ pass
9
+
10
+ @abstractmethod
11
+ def get_all_configs(self) -> Optional[dict]:
12
+ pass
@@ -0,0 +1,52 @@
1
+ import logging
2
+ from typing import Optional
3
+
4
+ from .ConfigurationManagerInterface import ConfigurationManagerInterface
5
+
6
+ class DummyConfigurationManager(ConfigurationManagerInterface):
7
+ """
8
+ A class to manage local and remote configuration stored in the 'config' database.
9
+ """
10
+
11
+ def __init__(
12
+ self,
13
+ worker_id: str,
14
+ worker_token: str,
15
+ server_host: str,
16
+ server_port: str,
17
+ rtmp_server_url: str,
18
+ rabbitmq_host: str,
19
+ rabbitmq_port: str,
20
+ rabbitmq_username: str,
21
+ rabbitmq_password: str,
22
+ logger: logging.Logger
23
+ ):
24
+ self._logger = logger
25
+ self._worker_id = worker_id
26
+ self._worker_token = worker_token
27
+ self._server_host = server_host
28
+ self._server_port = server_port
29
+ self._rtmp_server_url = rtmp_server_url
30
+ self._rabbitmq_host = rabbitmq_host
31
+ self._rabbitmq_port = rabbitmq_port
32
+ self._rabbitmq_username = rabbitmq_username
33
+ self._rabbitmq_password = rabbitmq_password
34
+
35
+ self._config = {
36
+ "worker_id": self._worker_id,
37
+ "token": self._worker_token,
38
+ "server_host": self._server_host,
39
+ "server_port": str(self._server_port),
40
+ "rtmp_server": self._rtmp_server_url,
41
+ "rabbitmq_host": self._rabbitmq_host,
42
+ "rabbitmq_port": self._rabbitmq_port,
43
+ "rabbitmq_username": self._rabbitmq_username,
44
+ "rabbitmq_password": self._rabbitmq_password,
45
+ }
46
+
47
+
48
+ def get_config(self, key: str) -> str:
49
+ return self._config[key]
50
+
51
+ def get_all_configs(self) -> Optional[dict]:
52
+ return self._config
@@ -31,7 +31,7 @@ class ConnectionInfoClient(GrpcClientBase):
31
31
  try:
32
32
  if not self.stub:
33
33
  raise Exception("Not connected to manager")
34
-
34
+
35
35
  # Prepare the request
36
36
  request = GetWorkerConnectionInfoRequest(token=self.token)
37
37
 
@@ -41,6 +41,7 @@ class GrpcClientBase:
41
41
  }
42
42
 
43
43
  def __init__(self, server_host: str, server_port: int = 50051, max_retries: int = 3):
44
+ self.stub = None
44
45
  self.server_address = f"{server_host}:{server_port}"
45
46
  self.channel: Optional[grpc.Channel] = None
46
47
  self.connected = False
@@ -0,0 +1,55 @@
1
+ from PIL import Image
2
+ import io
3
+ import numpy as np
4
+
5
+ # Tunables (adjust once, then forget)
6
+ MIN_IMAGE_KB = 10
7
+ GRAY_RATIO_THRESHOLD = 0.55
8
+ GRAY_MIN = 90
9
+ GRAY_MAX = 210
10
+ GRAY_DELTA = 4
11
+
12
+ def validate_image_gray_area(image_bytes: bytes) -> bool:
13
+ print(len(image_bytes))
14
+ # ---- Stage 1: size check (O(1)) ----
15
+ if len(image_bytes) < (MIN_IMAGE_KB * 1024):
16
+ return False
17
+
18
+ # ---- Stage 2: structural integrity (cheap) ----
19
+ try:
20
+ with Image.open(io.BytesIO(image_bytes)) as img:
21
+ img.verify()
22
+ except Exception:
23
+ return False
24
+
25
+ # ---- Stage 3: partial pixel decode (cropped, fast) ----
26
+ try:
27
+ with Image.open(io.BytesIO(image_bytes)) as img:
28
+ img = img.convert("RGB")
29
+
30
+ w, h = img.size
31
+ if w == 0 or h == 0:
32
+ return False
33
+
34
+ # Sample bottom 35% where corruption usually appears
35
+ crop_y = int(h * 0.65)
36
+ img_crop = img.crop((0, crop_y, w, h))
37
+
38
+ # Downscale aggressively (huge speedup)
39
+ img_small = img_crop.resize((w // 8, h // 8))
40
+
41
+ arr = np.asarray(img_small, dtype=np.uint8)
42
+
43
+ r, g, b = arr[..., 0], arr[..., 1], arr[..., 2]
44
+
45
+ gray_mask = (
46
+ (np.abs(r - g) < GRAY_DELTA) &
47
+ (np.abs(r - b) < GRAY_DELTA) &
48
+ (r > GRAY_MIN) & (r < GRAY_MAX)
49
+ )
50
+
51
+ gray_ratio = gray_mask.mean()
52
+ return gray_ratio < GRAY_RATIO_THRESHOLD
53
+
54
+ except Exception:
55
+ return False
@@ -7,6 +7,7 @@ from typing import Dict
7
7
  from ..services.WorkerSourcePipelineClient import WorkerSourcePipelineClient
8
8
  from ..services.GrpcClientManager import GrpcClientManager
9
9
  from ..repositories.DatasetSourceRepository import DatasetSourceRepository
10
+ from ..util.CorruptedImageValidator import validate_image_gray_area
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
@@ -96,6 +97,9 @@ class DatasetSourceThread:
96
97
  try:
97
98
  # Get frame from source
98
99
  frame_bytes = self._get_frame_from_source(self.dataset_source.worker_source_url)
100
+ if not validate_image_gray_area(frame_bytes):
101
+ logger.warning(f"⚠️ [APP] Detected gray area corruption in image while processing {self.dataset_source.dataset_name} (ID: {self.dataset_source.id})")
102
+ return True
99
103
 
100
104
  if frame_bytes:
101
105
  # Generate unique filename
@@ -88,10 +88,6 @@ class RabbitMQListener:
88
88
  self.exchange_name = exchange_name
89
89
  self.queue_name = queue_name
90
90
 
91
- if self.listener_thread and self.listener_thread.is_alive():
92
- logger.warning("⚠️ [APP] RabbitMQ listener is already running.")
93
- return
94
-
95
91
  # Clean up any dead thread reference
96
92
  if self.listener_thread and not self.listener_thread.is_alive():
97
93
  self.listener_thread = None
@@ -9,14 +9,9 @@ try:
9
9
  except RuntimeError:
10
10
  pass
11
11
 
12
- from .initializer.AppInitializer import AppInitializer
13
12
  from .worker.WorkerManager import WorkerManager
14
- from .config.ConfigurationManager import ConfigurationManager
15
- from .util.HardwareID import HardwareID
13
+ from .config.ConfigurationManagerInterface import ConfigurationManagerInterface
16
14
  from .services.GrpcClientBase import set_auth_failure_callback
17
- from .database.DatabaseManager import set_storage_path
18
- from . import models
19
-
20
15
 
21
16
  class WorkerService:
22
17
  """
@@ -26,8 +21,9 @@ class WorkerService:
26
21
 
27
22
  def __init__(
28
23
  self,
24
+ configuration_manager: ConfigurationManagerInterface,
29
25
  server_host: str = "be.vision.sindika.co.id",
30
- token: str = None,
26
+ token: str = "",
31
27
  system_usage_interval: int = 30,
32
28
  rtmp_server: str = "rtmp://live.vision.sindika.co.id:1935/live",
33
29
  storage_path: str = "data",
@@ -44,9 +40,8 @@ class WorkerService:
44
40
  storage_path: Storage path for databases and files (default: 'data')
45
41
  server_port: gRPC server port (default: 50051)
46
42
  """
47
- # Set the global storage path before any database operations
48
- set_storage_path(storage_path)
49
43
 
44
+ self._configuration_manager = configuration_manager
50
45
  self.logger = self._setup_logging()
51
46
  self.worker_manager = None
52
47
  self.running = False
@@ -89,84 +84,6 @@ class WorkerService:
89
84
 
90
85
  return logging.getLogger(__name__)
91
86
 
92
- def _initialize_configuration(self):
93
- """Initialize the application configuration."""
94
- self.logger.info("🚀 [APP] Initializing application...")
95
-
96
- # Initialize database
97
- ConfigurationManager.init_database()
98
-
99
- # Load all configurations at once
100
- config = ConfigurationManager.get_all_configs()
101
-
102
- # Use the server_host parameter directly
103
- server_host = self.server_host
104
- self.logger.info(f"🌐 [APP] Using server host: {server_host}")
105
-
106
- # Check if configuration exists
107
- if not config:
108
- self.logger.info("⚙️ [APP] Configuration not found. Performing first-time setup...")
109
-
110
- # Get hardware ID
111
- hardware_id = HardwareID.get_unique_id()
112
-
113
- self.logger.info(f"🖥️ [APP] Detected Hardware ID: {hardware_id}")
114
- self.logger.info(f"🌐 [APP] Using Server Host: {server_host}")
115
-
116
- # Check if token is provided
117
- if not self.token:
118
- raise ValueError("Token is required for worker initialization. Please provide a token obtained from the frontend.")
119
-
120
- # Initialize with token
121
- AppInitializer.initialize_configuration(hardware_id, server_host, self.token)
122
-
123
- # Set server_port in config for first-time setup
124
- ConfigurationManager.set_config("server_port", str(self.server_port))
125
-
126
- # Get configuration
127
- config = ConfigurationManager.get_all_configs()
128
- else:
129
- # Check if server_host, server_port, or token has changed and update if needed
130
- config_updated = False
131
-
132
- if config['server_host'] != server_host:
133
- ConfigurationManager.set_config("server_host", server_host)
134
- config_updated = True
135
- self.logger.info(f"✅ [APP] Updated server host to: {server_host}")
136
-
137
- # Check if server_port has changed and update if needed
138
- if str(config.get('server_port')) != str(self.server_port):
139
- ConfigurationManager.set_config("server_port", str(self.server_port))
140
- config_updated = True
141
- self.logger.info(f"✅ [APP] Updated server port to: {self.server_port}")
142
-
143
- # Check if token has changed and update if needed
144
- if self.token and config.get('token') != self.token:
145
- ConfigurationManager.set_config("token", self.token)
146
- config_updated = True
147
- self.logger.info("✅ [APP] Updated authentication token")
148
-
149
- if config_updated:
150
- config = ConfigurationManager.get_all_configs()
151
- self.logger.info("✅ [APP] Configuration updated successfully")
152
- else:
153
- self.logger.info("✅ [APP] Configuration found. No changes needed.")
154
-
155
- # Always fetch connection info on startup to check for updates
156
- self.logger.info("🔄 [APP] Checking for connection info updates...")
157
- token_to_use = self.token if self.token else config.get('token')
158
- if token_to_use:
159
- AppInitializer.update_connection_info(server_host, self.server_port, token_to_use)
160
- # Reload config after potential updates
161
- config = ConfigurationManager.get_all_configs()
162
- else:
163
- self.logger.warning("⚠️ [APP] No token available to fetch connection info updates")
164
-
165
- # Add runtime parameters to config
166
- config['rtmp_server'] = self.rtmp_server
167
-
168
- return config
169
-
170
87
  def initialize(self) -> bool:
171
88
  """
172
89
  Initialize the worker service components.
@@ -178,9 +95,9 @@ class WorkerService:
178
95
  self.logger.info("Worker service initialization started")
179
96
 
180
97
  # Initialize configuration
181
- self.config = self._initialize_configuration()
182
-
183
- if not self.config:
98
+ self.config = self._configuration_manager.get_all_configs()
99
+ print(self.config)
100
+ if self.config is None:
184
101
  raise RuntimeError("Failed to initialize configuration")
185
102
 
186
103
  # Initialize WorkerManager
@@ -275,12 +192,12 @@ def main():
275
192
  args = parser.parse_args()
276
193
 
277
194
  # Create and run worker service
278
- service = WorkerService(
279
- server_host=args.server_host,
280
- server_port=args.server_port,
281
- system_usage_interval=args.system_usage_interval
282
- )
283
- service.run()
195
+ # service = WorkerService(
196
+ # server_host=args.server_host,
197
+ # server_port=args.server_port,
198
+ # system_usage_interval=args.system_usage_interval
199
+ # )
200
+ # service.run()
284
201
 
285
202
 
286
203
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedo-vision-worker
3
- Version: 1.3.7
3
+ Version: 1.3.9
4
4
  Summary: Nedo Vision Worker Service Library for AI Vision Processing
5
5
  Author-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
6
6
  Maintainer-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
@@ -40,6 +40,7 @@ Requires-Dist: protobuf>=3.20.0
40
40
  Requires-Dist: psutil>=5.9.0
41
41
  Requires-Dist: requests>=2.28.0
42
42
  Requires-Dist: SQLAlchemy>=1.4.0
43
+ Requires-Dist: Pillow>=12.1.0
43
44
  Requires-Dist: pynvml>=11.4.1; platform_system != "Darwin" or platform_machine != "arm64"
44
45
  Provides-Extra: opencv
45
46
  Requires-Dist: opencv-python>=4.6.0; platform_machine not in "aarch64 armv7l" and extra == "opencv"
@@ -1,13 +1,14 @@
1
- nedo_vision_worker/__init__.py,sha256=SoFfn_lr0mgpPFESSvD3Wg3PDoyi4bwdY5gGVadDJPg,203
2
- nedo_vision_worker/cli.py,sha256=ddWspJmSgVkcUYvRdkvTtMNuMTDvNCqLLuMVU9KE3Ik,7457
1
+ nedo_vision_worker/__init__.py,sha256=vQPFkMA2dVYlXMWnsaaP0bx0guzxL0prnYAA2hObGUE,203
2
+ nedo_vision_worker/bootstrap.py,sha256=CUwNtb_mf1VGwO1-ujai7ZNN0ojiO3XQzV50JEwmS3U,1430
3
+ nedo_vision_worker/cli.py,sha256=L0hrjSdU4yfoC-KuxkYJ3orpwH_dWzdmGMTHzjOj5MM,2539
3
4
  nedo_vision_worker/doctor.py,sha256=wNkpe8gLVd76Y_ViyK2h1ZFdqeSl37MnzZN5frWKu30,48410
4
- nedo_vision_worker/worker_service.py,sha256=9zz8hKwDwqwpfS0KPQfftGJtRci0uj_wiwcr_TGf-E0,11039
5
- nedo_vision_worker/config/ConfigurationManager.py,sha256=QrQaQ9Cdjpkcr2JE_miyrWJIZmMgZwJYBz-wE45Zzes,8011
5
+ nedo_vision_worker/worker_service.py,sha256=4SoWqjuz7lQKByU48-yBLV0CeTmX9LIJ5uIc-vmn6ck,7318
6
+ nedo_vision_worker/config/ConfigurationManager.py,sha256=NQD6kXmBrahuMIEhvUr1zrcB60HlQxTmWvBY1dD28PI,8392
7
+ nedo_vision_worker/config/ConfigurationManagerInterface.py,sha256=doV1Qv6e2edL5Llu-JOF_vhmB87iG-yTXL_GYmlvv9M,270
8
+ nedo_vision_worker/config/DummyConfigurationManager.py,sha256=J1Ur5AD4YKl6CmdJ78SufSw5gdTQURUQtww61FxIoks,1685
6
9
  nedo_vision_worker/config/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
7
10
  nedo_vision_worker/database/DatabaseManager.py,sha256=j2koXo1fnMmAyQnY4sv4txfZR8qIzrPyev-sQ4HBaOQ,9478
8
11
  nedo_vision_worker/database/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
9
- nedo_vision_worker/initializer/AppInitializer.py,sha256=6UVdjiuayziPYZ7JkQ436z7-9sHj7J3jtp6lfQsu-DU,5698
10
- nedo_vision_worker/initializer/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
11
12
  nedo_vision_worker/models/__init__.py,sha256=6ZH2W1Jcy4o6xBPqFPcyxRU2UJ5Zvw_kfO38yLLGtHA,796
12
13
  nedo_vision_worker/models/ai_model.py,sha256=9muyZL9AxtX417-tYUiw8bgvFPtqdXgEAq-hm_mLxGY,2277
13
14
  nedo_vision_worker/models/auth.py,sha256=hBMchk6ATy8Wc3fUWLpYRbeNiteb43t2hIub0cCFap8,456
@@ -48,11 +49,11 @@ nedo_vision_worker/repositories/WorkerSourcePipelineRepository.py,sha256=xfmEvgn
48
49
  nedo_vision_worker/repositories/WorkerSourceRepository.py,sha256=AhAJLAacMFdsOgtQNiu7Pahl1DAGI0T1THHeUlKwQJc,2385
49
50
  nedo_vision_worker/repositories/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
50
51
  nedo_vision_worker/services/AIModelClient.py,sha256=lxRNax6FR-pV0G1NpJnlaqjbQeu3kRolIUNSw1RkoZA,15406
51
- nedo_vision_worker/services/ConnectionInfoClient.py,sha256=toC9zuY2Hrx1Cwq8Gycy_iFlaG1DvFT4qewlLlitpEQ,2214
52
+ nedo_vision_worker/services/ConnectionInfoClient.py,sha256=udvcKVjZaWfmMbC3HM1cybU5fz9ZidnJGnHj6OSr0uQ,2226
52
53
  nedo_vision_worker/services/DatasetSourceClient.py,sha256=O5a7onxFl0z47zXaMXWxHAMPuuc-i_vzkd2w5fwrukc,3319
53
54
  nedo_vision_worker/services/DirectDeviceToRTMPStreamer.py,sha256=Ypcc0fh1WiUMkICN_KRRAvGmeGkgF8jQ06gfiRYReW4,24747
54
55
  nedo_vision_worker/services/FileToRTMPServer.py,sha256=0hY5pmeAzLw_d3uPR2Qp6gSAYb4rJHiAunuNe08OvkM,2870
55
- nedo_vision_worker/services/GrpcClientBase.py,sha256=hPyxOGw3aGSW1FhmY3wp3Iq8U1MArXBmvEMdmd63NZ4,6827
56
+ nedo_vision_worker/services/GrpcClientBase.py,sha256=RkJDGRsXu5HalMDR8cOsIaoFf5tA_cLTkh5euBPyo2M,6852
56
57
  nedo_vision_worker/services/GrpcClientManager.py,sha256=DLXekmxlQogLo8V9-TNDXtyHT_UG-BaggqwsIups55k,5568
57
58
  nedo_vision_worker/services/GrpcConnection.py,sha256=UNjaUC4ZcXuteHQx8AAAL5ymYkT1OpoIvyCYPUc3tCI,4915
58
59
  nedo_vision_worker/services/ImageUploadClient.py,sha256=T353YsRfm74G7Mh-eWr5nvdQHXTfpKwHJFmNW8HyjT8,3019
@@ -70,6 +71,7 @@ nedo_vision_worker/services/WorkerSourcePipelineClient.py,sha256=9qj65uBujCVoUE3
70
71
  nedo_vision_worker/services/WorkerSourceUpdater.py,sha256=RUNkiL1FRrKkW4UDm4i4i8ShgJy__MhTTrNlfouDAZc,8892
71
72
  nedo_vision_worker/services/WorkerStatusClient.py,sha256=7kC5EZjEBwWtHOE6UQ29OPCpYnv_6HSuH7Tc0alK_2Q,2531
72
73
  nedo_vision_worker/services/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
74
+ nedo_vision_worker/util/CorruptedImageValidator.py,sha256=F58L-thVFiz_sfi84L6xc9y2njrEuiO2d9AwzSe03hk,1529
73
75
  nedo_vision_worker/util/EncoderSelector.py,sha256=-9lZwVmiKzJr1cELeuCXi-jRonty2bpociZq4KDScmA,3399
74
76
  nedo_vision_worker/util/FFmpegUtil.py,sha256=QnQrzurmllzGb7SlAAYCrzKBUblweoFU-0h-X-32IYg,1829
75
77
  nedo_vision_worker/util/HardwareID.py,sha256=rSW8-6stm7rjXEdkYGqXMUn56gyw62YiWnSwZQVCCLM,4315
@@ -83,19 +85,19 @@ nedo_vision_worker/worker/CoreActionWorker.py,sha256=lb7zPY3yui6I3F4rX4Ii7JwpWZa
83
85
  nedo_vision_worker/worker/DataSenderWorker.py,sha256=TpMLhjpuuW-70TP6BzKpR9irPqVr9dDyFsBAA5s2N3U,8784
84
86
  nedo_vision_worker/worker/DataSyncWorker.py,sha256=LmDPt2J1frmXwuR46L6b0MjlFOHfgG-4_0MGQa78zF4,6288
85
87
  nedo_vision_worker/worker/DatasetFrameSender.py,sha256=1SFYj8LJFNi-anBTapsbq8U_NGMM7mnoMKg9NeFAHys,8087
86
- nedo_vision_worker/worker/DatasetFrameWorker.py,sha256=vzl6bab58a8MyXm6lfDom-kuGKwRj70moFIizePAnU0,18931
88
+ nedo_vision_worker/worker/DatasetFrameWorker.py,sha256=gVgMEnwZsLUsnOIml4XJGTBk5YkB0cjDYEp0X14OMHQ,19255
87
89
  nedo_vision_worker/worker/PPEDetectionManager.py,sha256=sXeOjvhCzi4oUhDZwH5-8DSxI9b__Jp0de8QksgaYGw,6063
88
90
  nedo_vision_worker/worker/PipelineActionWorker.py,sha256=xgvryjKtEsMj4BKqWzDIaK_lFny-DfMCj5Y2DxHnWww,5651
89
91
  nedo_vision_worker/worker/PipelineImageWorker.py,sha256=J8VBUG0cwcH3qOJp2zTl30B-XhmPFyvJLjxitKJYq0E,5642
90
92
  nedo_vision_worker/worker/PipelinePreviewWorker.py,sha256=owFiBbktcOZkdImQeykZSeBIR2-mpt6HNkmYIkLRKzE,6397
91
- nedo_vision_worker/worker/RabbitMQListener.py,sha256=9gR49MDplgpyb-D5HOH0K77-DJQFvhS2E7biL92SjSU,6950
93
+ nedo_vision_worker/worker/RabbitMQListener.py,sha256=Gwn7VpRg0fMZ0fva98eOnTzZ4HPFf2i_ZCUgwfTdyYQ,6768
92
94
  nedo_vision_worker/worker/RestrictedAreaManager.py,sha256=Pz2M9KPSMa1QPXZeYhyN9ih4eX6wmkNS_ZPu5KrvNxY,5927
93
95
  nedo_vision_worker/worker/SystemUsageManager.py,sha256=mkh4sT-HkIEY1CJHMEG6LP9ATu39YXvLRLyf995OkoQ,5315
94
96
  nedo_vision_worker/worker/VideoStreamWorker.py,sha256=5n6v1PNO7IB-jj_McALLkUP-cBjJoIEw4UiSAs3vTb0,7606
95
97
  nedo_vision_worker/worker/WorkerManager.py,sha256=2bxXi19fp3p1qjYBStYRdVVgko8dnevXx1_M_sqH5og,5521
96
98
  nedo_vision_worker/worker/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
97
- nedo_vision_worker-1.3.7.dist-info/METADATA,sha256=PFsTaJvY-oOl6159V_lzBNIcFG7lKODrjYWNsYqzPAU,14728
98
- nedo_vision_worker-1.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
99
- nedo_vision_worker-1.3.7.dist-info/entry_points.txt,sha256=LrglS-8nCi8C_PL_pa6uxdgCe879hBETHDVXAckvs-8,60
100
- nedo_vision_worker-1.3.7.dist-info/top_level.txt,sha256=vgilhlkyD34YsEKkaBabmhIpcKSvF3XpzD2By68L-XI,19
101
- nedo_vision_worker-1.3.7.dist-info/RECORD,,
99
+ nedo_vision_worker-1.3.9.dist-info/METADATA,sha256=nTYIlYmWee5VO9kjFPGbcsrNaYcamNkKc89ZzwKzF-o,14758
100
+ nedo_vision_worker-1.3.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
101
+ nedo_vision_worker-1.3.9.dist-info/entry_points.txt,sha256=LrglS-8nCi8C_PL_pa6uxdgCe879hBETHDVXAckvs-8,60
102
+ nedo_vision_worker-1.3.9.dist-info/top_level.txt,sha256=vgilhlkyD34YsEKkaBabmhIpcKSvF3XpzD2By68L-XI,19
103
+ nedo_vision_worker-1.3.9.dist-info/RECORD,,
@@ -1,138 +0,0 @@
1
- import logging
2
- import re
3
- import uuid
4
- import grpc
5
- from ..config.ConfigurationManager import ConfigurationManager
6
- from ..util.PlatformDetector import PlatformDetector
7
- from ..util.Networking import Networking
8
- from ..services.ConnectionInfoClient import ConnectionInfoClient
9
- from ..database.DatabaseManager import DatabaseManager
10
-
11
-
12
- class AppInitializer:
13
- @staticmethod
14
- def validate_uuid(value):
15
- """Validate if the provided value is a valid UUID."""
16
- try:
17
- uuid.UUID(value)
18
- return value
19
- except ValueError:
20
- raise ValueError(f"Invalid device ID format: {value}. Must be a valid UUID.")
21
-
22
- @staticmethod
23
- def validate_server_host(value):
24
- """Validate if the server host is a valid domain name or IP address."""
25
- domain_regex = (
26
- r"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*"
27
- r"([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$"
28
- )
29
- ip_regex = (
30
- r"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\."
31
- r"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\."
32
- r"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\."
33
- r"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
34
- )
35
- if re.match(domain_regex, value) or re.match(ip_regex, value):
36
- return value
37
- raise ValueError(f"Invalid server host: {value}. Must be a valid domain or IP address.")
38
-
39
- @staticmethod
40
- def initialize_configuration(device_id: str, server_host: str, token: str):
41
- """
42
- Initialize the application configuration using the provided token
43
- and saving configuration data locally.
44
- """
45
- try:
46
- # Validate inputs
47
- AppInitializer.validate_uuid(device_id)
48
- AppInitializer.validate_server_host(server_host)
49
-
50
- # Get connection info using the ConnectionInfoClient
51
- connection_client = ConnectionInfoClient(server_host, 50051, token)
52
- connection_result = connection_client.get_connection_info()
53
-
54
- if not connection_result["success"]:
55
- logging.error(f"Device connection info failed: {connection_result['message']}")
56
- return
57
-
58
- worker_id = connection_result.get('id')
59
- if not worker_id:
60
- raise ValueError("No worker_id returned from connection info!")
61
-
62
- ConfigurationManager.set_config_batch({
63
- "worker_id": worker_id,
64
- "server_host": server_host,
65
- "token": token,
66
- "rabbitmq_host": connection_result['rabbitmq_host'],
67
- "rabbitmq_port": str(connection_result['rabbitmq_port']),
68
- "rabbitmq_username": connection_result['rabbitmq_username'],
69
- "rabbitmq_password": connection_result['rabbitmq_password']
70
- })
71
- ConfigurationManager.print_config()
72
-
73
- except ValueError as ve:
74
- logging.error(f"Validation error: {ve}")
75
- except grpc.RpcError as ge:
76
- logging.error(f"Grpc Error: {ge}")
77
- except Exception as e:
78
- logging.error(f"Unexpected error during initialization: {e}")
79
-
80
- @staticmethod
81
- def update_connection_info(server_host: str, server_port: int, token: str):
82
- """
83
- Fetch and update connection information (RabbitMQ credentials) from the server.
84
- This should be called on startup to ensure credentials are up-to-date.
85
-
86
- Args:
87
- server_host: The server hostname or IP address
88
- server_port: The gRPC server port
89
- token: Authentication token for the worker
90
-
91
- Returns:
92
- bool: True if update was successful, False otherwise
93
- """
94
- try:
95
- # Validate server host
96
- AppInitializer.validate_server_host(server_host)
97
-
98
- # Get connection info using the ConnectionInfoClient
99
- connection_client = ConnectionInfoClient(server_host, server_port, token)
100
- connection_result = connection_client.get_connection_info()
101
-
102
- if not connection_result["success"]:
103
- logging.error(f"Failed to fetch connection info: {connection_result['message']}")
104
- return False
105
-
106
- # Check if any RabbitMQ credentials have changed
107
- current_config = ConfigurationManager.get_all_configs()
108
- config_updated = False
109
-
110
- rabbitmq_fields = {
111
- 'rabbitmq_host': connection_result['rabbitmq_host'],
112
- 'rabbitmq_port': str(connection_result['rabbitmq_port']),
113
- 'rabbitmq_username': connection_result['rabbitmq_username'],
114
- 'rabbitmq_password': connection_result['rabbitmq_password']
115
- }
116
-
117
- for field, new_value in rabbitmq_fields.items():
118
- if current_config.get(field) != new_value:
119
- ConfigurationManager.set_config(field, new_value)
120
- config_updated = True
121
- logging.info(f"✅ [APP] Updated {field}")
122
-
123
- if config_updated:
124
- logging.info("✅ [APP] RabbitMQ connection info updated successfully")
125
- else:
126
- logging.info("✅ [APP] RabbitMQ connection info is up-to-date")
127
-
128
- return True
129
-
130
- except ValueError as ve:
131
- logging.error(f"Validation error: {ve}")
132
- return False
133
- except grpc.RpcError as ge:
134
- logging.error(f"gRPC Error: {ge}")
135
- return False
136
- except Exception as e:
137
- logging.error(f"Unexpected error updating connection info: {e}")
138
- return False
@@ -1 +0,0 @@
1
-