diff --git a/Services.md b/Services.md index 0b01ecb..fdc754c 100644 --- a/Services.md +++ b/Services.md @@ -20,11 +20,11 @@ In your configuration ini file you will add the following section: ```ini [application_name.service_id] __class = digitalpy.core.service_management.domain.model.service_configuration.ServiceConfiguration -status = STOPPED +status = [RUNNING|STOPPED] name = MyNewService port = 8443 host = 0.0.0.0 -protocol = TCP +protocol = [FlaskHTTPNetworkBlueprints|TCPNetwork|Reticulum] ``` Next you will add or update the ServiceManagementConfiguration section as follows: diff --git a/digitalpy/core/core_config.ini b/digitalpy/core/core_config.ini index 7352809..326fee1 100644 --- a/digitalpy/core/core_config.ini +++ b/digitalpy/core/core_config.ini @@ -125,6 +125,10 @@ blueprint_import_base = digitalpy.blueprints ; NETWORK OBJECTS ; the default tcp_network +[Reticulum] +__class = digitalpy.core.network.impl.network_reticulum.ReticulumNetwork +client = DefaultClient + [TCPNetwork] __class = digitalpy.core.network.impl.network_tcp.TCPNetwork client = DefaultClient diff --git a/digitalpy/core/network/impl/network_reticulum.py b/digitalpy/core/network/impl/network_reticulum.py new file mode 100644 index 0000000..0af87b2 --- /dev/null +++ b/digitalpy/core/network/impl/network_reticulum.py @@ -0,0 +1,197 @@ +"""This file contains the reticulum network implementation. +This consits of two main classes +1. reticulum manager which is responsible for running the reticulum network stack in a separate process and exposing to the network. +2. reticulum network which is responsible for exposing the network interface to a service +""" + +import threading +import RNS +import LXMF +import os +import time +import zmq +from multiprocessing import Queue +from typing import Callable +from digitalpy.core.zmanager.response import Response +from digitalpy.core.domain.object_id import ObjectId +from digitalpy.core.network.domain.client_status import ClientStatus +from digitalpy.core.domain.domain.network_client import NetworkClient +from digitalpy.core.main.object_factory import ObjectFactory +from digitalpy.core.network.network_sync_interface import NetworkSyncInterface +from digitalpy.core.zmanager.request import Request + +APP_NAME = LXMF.APP_NAME + ".delivery" + +class AnnounceHandler: + def __init__(self, identities): + self.aspect_filter = APP_NAME # Filter for LXMF announcements + self.identities = identities # Dictionary to store identities + + def received_announce(self, destination_hash, announced_identity, app_data): + if destination_hash not in self.identities: + self.identities[destination_hash] = announced_identity + +class ReticulumNetwork(NetworkSyncInterface): + def __init__(self): + self._storage_path = None + self._identity_path = None + self._announcer_thread = None + self.message_queue = Queue() + self._clients = {} + self._ret = None + self._lxm_router = None + self._identity = None + self._my_identity = None + self._identities = {} + + def initialize_network(self, _, _port, storage_path, identity_path, service_desc): + self._storage_path = storage_path + self._identity_path = identity_path + self._ret = RNS.Reticulum() + self._lxm_router = LXMF.LXMRouter(storagepath=self._storage_path) + RNS.Transport.register_announce_handler(AnnounceHandler(self._identities)) + self._identity = self._load_or_generate_identity() + self._my_identity = self._lxm_router.register_delivery_identity(self._identity) + self._lxm_router.register_delivery_callback(self._ret_deliver) + announcer_thread = threading.Thread(target=self._announcer) + announcer_thread.start() + self._service_desc = service_desc + + def _load_or_generate_identity(self): + if os.path.exists(self._identity_path): + try: + return RNS.Identity.from_file(self._identity_path) + except RNS.InvalidIdentityFile: + pass + identity = RNS.Identity() + identity.to_file(self._identity_path) + return identity + + def _get_client(self, identity: RNS.Identity) -> NetworkClient: + if identity.hash in self._clients: + return self._clients[identity.hash] + else: + client = self._register_new_client(identity.hash) + self._clients[identity.hash] = client + self._identities[identity.hash] = identity + return client + + def _ret_deliver(self, message: LXMF.LXMessage): + try: + # validate the message + if message.signature_validated: + validated = True + elif message.unverified_reason == LXMF.LXMessage.SIGNATURE_INVALID: + validated = False + elif message.unverified_reason == LXMF.LXMessage.SOURCE_UNKNOWN: + validated = False + else: + validated = False + + # deliver the message to the network + if validated and message.content is not None and message.content != b"": + req: Request = ObjectFactory.get_new_instance("Request") + req.set_value("body", message.content.decode("utf-8")) + req.set_action("reticulum_message") + req.set_value("client", self._get_client(message.source.identity)) + self.message_queue.put(req, block=False, timeout=0) + except Exception as e: + print(e) + + def _register_new_client(self, destination_hash: bytes): + """Register a new client to the network. + Args: + destination_hash (bytes): The hash of the client destination to register. + """ + oid = ObjectId("network_client", id=str(destination_hash)) + client: NetworkClient = ObjectFactory.get_new_instance( + "DefaultClient", dynamic_configuration={"oid": oid} + ) + client.id = destination_hash + client.status = ClientStatus.CONNECTED + client.service_id = self._service_desc.name + client.protocol = self._service_desc.protocol + return client + + def _get_client_identity(self, message: LXMF.LXMessage) -> bytes: + """Get the identity of the client that sent the message. This is used for IAM and client tracking. + Args: + message (LXMF.LXMessage): The message to extract the identity from. + + Returns: + bytes: The identity of the client as bytes + """ + return message.source.identity.hash + + def _announcer(self, interval: int = 60): + """Announce the reticulum network to the network.""" + while True: + try: + self._my_identity.announce() + except Exception as e: + pass + time.sleep(interval) + + def _send_message_to_all_clients(self, message: str): + for identity in self._clients.values(): + dest = RNS.Destination( + self._identities[identity.id], + RNS.Destination.OUT, + RNS.Destination.SINGLE, + "lxmf", + "delivery", + ) + msg = LXMF.LXMessage( + destination=dest, + source=self._my_identity, + content=message.encode("utf-8"), + desired_method=LXMF.LXMessage.DIRECT, + ) + self._lxm_router.handle_outbound(msg) + + def _send_message_to_client(self, message: dict, client: NetworkClient): + identity = self._identities.get(client.id) + if identity is not None: + dest = RNS.Destination( + identity, + RNS.Destination.OUT, + RNS.Destination.SINGLE, + "lxmf", + "delivery", + ) + msg = LXMF.LXMessage( + destination=dest, + source=self._my_identity, + content=message.encode("utf-8"), + desired_method=LXMF.LXMessage.DIRECT, + ) + self._lxm_router.handle_outbound(msg) + + def service_connections(self, max_requests=1000, blocking=False, timeout=0): + start_time = time.time() + messages = [] + if self.message_queue.empty(): + return [] + messages.append(self.message_queue.get(block=blocking, timeout=timeout)) + while time.time() - start_time < timeout and len(messages) < max_requests: + try: + message = self.message_queue.get(block=False) + messages.append(message) + except Exception as e: + break + return messages + + def send_response(self, response): + if response.get_value("client") is not None: + self._send_message_to_client(response.get_value("message"), response.get_value("client")) + else: + self._send_message_to_all_clients(response.get_value("message")) + + def receive_message(self, blocking = False): + return self.message_queue.get(block=blocking) + + def receive_message_from_client(self, client, blocking = False): + raise NotImplementedError + + def teardown_network(self): + pass diff --git a/examples/reticulum_app/blueprints/__init__.py b/examples/reticulum_app/blueprints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/Chat_facade.py b/examples/reticulum_app/components/Chat/Chat_facade.py new file mode 100644 index 0000000..74b3f39 --- /dev/null +++ b/examples/reticulum_app/components/Chat/Chat_facade.py @@ -0,0 +1,82 @@ +from digitalpy.core.component_management.impl.default_facade import DefaultFacade +from digitalpy.core.zmanager.impl.async_action_mapper import AsyncActionMapper +from digitalpy.core.zmanager.impl.default_action_mapper import DefaultActionMapper +from digitalpy.core.zmanager.request import Request +from digitalpy.core.zmanager.response import Response +from .controllers.chat_controller import ChatController +from .configuration.chat_constants import ( + ACTION_MAPPING_PATH, + LOGGING_CONFIGURATION_PATH, + INTERNAL_ACTION_MAPPING_PATH, + MANIFEST_PATH, + CONFIGURATION_PATH_TEMPLATE, + LOG_FILE_PATH, + ACTION_FLOW_PATH, +) + +from . import base + + +class Chat(DefaultFacade): + """ + """ + + def __init__(self, sync_action_mapper: DefaultActionMapper, request: Request, + response: Response, configuration, + action_mapper: AsyncActionMapper = None, # type: ignore + tracing_provider_instance=None): # type: ignore + super().__init__( + # the path to the external action mapping + action_mapping_path=str(ACTION_MAPPING_PATH), + # the path to the internal action mapping + internal_action_mapping_path=str(INTERNAL_ACTION_MAPPING_PATH), + # the path to the logger configuration + logger_configuration=str(LOGGING_CONFIGURATION_PATH), + # the package containing the base classes + base=base, # type: ignore + # the general action mapper (passed by constructor) + action_mapper=sync_action_mapper, + # the request object (passed by constructor) + request=request, + # the response object (passed by constructor) + response=response, + # the configuration object (passed by constructor) + configuration=configuration, + # log file path + log_file_path=LOG_FILE_PATH, + # the tracing provider used + tracing_provider_instance=tracing_provider_instance, + # the template for the absolute path to the model object definitions + configuration_path_template=CONFIGURATION_PATH_TEMPLATE, + # the path to the manifest file + manifest_path=str(MANIFEST_PATH), + # the general action mapper (passed by constructor) + action_flow_path=str(ACTION_FLOW_PATH), + ) + self.Chat_controller = ChatController( + request, response, sync_action_mapper, configuration) + + def initialize(self, request: Request, response: Response): + self.Chat_controller.initialize(request, response) + + return super().initialize(request, response) + + def execute(self, method=None): + try: + if hasattr(self, method): # type: ignore + print("executing method "+str(method)) # type: ignore + getattr(self, method)(**self.request.get_values()) # type: ignore + else: + self.request.set_value("logger", self.logger) + self.request.set_value("config_loader", self.config_loader) + self.request.set_value("tracer", self.tracer) + response = self.execute_sub_action(self.request.get_action()) + self.response.set_values(response.get_values()) + except Exception as e: + self.logger.fatal(str(e)) + + @DefaultFacade.public + def Chat(self, *args, **kwargs): + """Updates an existing Genre record. + """ + self.Chat_controller.Chat(*args, **kwargs) diff --git a/examples/reticulum_app/components/Chat/README.md b/examples/reticulum_app/components/Chat/README.md new file mode 100644 index 0000000..b99b4dd --- /dev/null +++ b/examples/reticulum_app/components/Chat/README.md @@ -0,0 +1,48 @@ +# FilmologyManagement Digital Component + +## Description +"Filmology is a test API that describes all the possible variant of a DAF + API model and his implementation as a DigitalPy application" + +## Configuration +1. copy the file FilmologyManagement_blueprint.py into your application blueprints folder +2. add the following to your configured core api flows in the configuration/object_configuration.ini file. +```ini +[ +; FilmologyManagement component flows +,FilmologyManagement__POSTMovie +,FilmologyManagement__DELETEMovie +,FilmologyManagement__GETMovie +,FilmologyManagement__PATCHMovie +,FilmologyManagement__GETDirectorId +,FilmologyManagement__POSTPoster +,FilmologyManagement__DELETEPoster +,FilmologyManagement__GETPoster +,FilmologyManagement__PATCHPoster +,FilmologyManagement__GETGenreId +,FilmologyManagement__POSTDate +,FilmologyManagement__DELETEDate +,FilmologyManagement__GETDate +,FilmologyManagement__PATCHDate +,FilmologyManagement__GETLanguageId +,FilmologyManagement__POSTDirector +,FilmologyManagement__DELETEDirector +,FilmologyManagement__GETDirector +,FilmologyManagement__PATCHDirector +,FilmologyManagement__GETDateId +,FilmologyManagement__POSTActor +,FilmologyManagement__DELETEActor +,FilmologyManagement__GETActor +,FilmologyManagement__PATCHActor +,FilmologyManagement__GETMovieId +,FilmologyManagement__POSTLanguage +,FilmologyManagement__DELETELanguage +,FilmologyManagement__GETLanguage +,FilmologyManagement__PATCHLanguage +,FilmologyManagement__GETPosterId +,FilmologyManagement__GETActorId +,FilmologyManagement__POSTGenre +,FilmologyManagement__DELETEGenre +,FilmologyManagement__GETGenre +,FilmologyManagement__PATCHGenre +] +``` diff --git a/examples/reticulum_app/components/Chat/base/__init__.py b/examples/reticulum_app/components/Chat/base/__init__.py new file mode 100644 index 0000000..4c1a065 --- /dev/null +++ b/examples/reticulum_app/components/Chat/base/__init__.py @@ -0,0 +1,4 @@ +"""This module contains all the supporting components without business logic +it should also be noted that the component action mapper must be exposed as action mapper. +""" +from .chat_action_mapper import ChatActionMapper as ActionMapper diff --git a/examples/reticulum_app/components/Chat/base/chat_action_mapper.py b/examples/reticulum_app/components/Chat/base/chat_action_mapper.py new file mode 100644 index 0000000..b911cfc --- /dev/null +++ b/examples/reticulum_app/components/Chat/base/chat_action_mapper.py @@ -0,0 +1,5 @@ +from digitalpy.core.zmanager.impl.default_action_mapper import DefaultActionMapper + + +class ChatActionMapper(DefaultActionMapper): + pass diff --git a/examples/reticulum_app/components/Chat/base/chat_domain.py b/examples/reticulum_app/components/Chat/base/chat_domain.py new file mode 100644 index 0000000..62784ef --- /dev/null +++ b/examples/reticulum_app/components/Chat/base/chat_domain.py @@ -0,0 +1,2 @@ +class ChatDomain(): + pass diff --git a/examples/reticulum_app/components/Chat/configuration/__init__.py b/examples/reticulum_app/components/Chat/configuration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/configuration/chat_constants.py b/examples/reticulum_app/components/Chat/configuration/chat_constants.py new file mode 100644 index 0000000..d24a8b8 --- /dev/null +++ b/examples/reticulum_app/components/Chat/configuration/chat_constants.py @@ -0,0 +1,35 @@ +""" this file contains all the constants used in the component """ + +import pathlib + +from string import Template + +COMPONENT_NAME = "chat" + +CONFIGURATION_FORMAT = "json" + +CURRENT_COMPONENT_PATH = pathlib.Path(__file__).absolute().parent.parent + +ACTION_MAPPING_PATH = CURRENT_COMPONENT_PATH / \ + "configuration/external_action_mapping.ini" + +ACTION_FLOW_PATH = ( + CURRENT_COMPONENT_PATH / "configuration/chat_flows.ini" +) + +INTERNAL_ACTION_MAPPING_PATH = CURRENT_COMPONENT_PATH / \ + "configuration/internal_action_mapping.ini" + +LOGGING_CONFIGURATION_PATH = CURRENT_COMPONENT_PATH / "configuration/logging.conf" + +LOG_FILE_PATH = CURRENT_COMPONENT_PATH / "logs" + +MANIFEST_PATH = CURRENT_COMPONENT_PATH / "configuration/manifest.ini" + +CONFIGURATION_PATH_TEMPLATE = Template( + str(CURRENT_COMPONENT_PATH / "configuration/model_definitions/$message_type") + + f".{CONFIGURATION_FORMAT}" +) + +DB_PATH = "sqlite:///" + str(CURRENT_COMPONENT_PATH / "persistence/chat.db") + diff --git a/examples/reticulum_app/components/Chat/configuration/chat_flows.ini b/examples/reticulum_app/components/Chat/configuration/chat_flows.ini new file mode 100644 index 0000000..4b5d3d1 --- /dev/null +++ b/examples/reticulum_app/components/Chat/configuration/chat_flows.ini @@ -0,0 +1,4 @@ +[ChatMessageFlow] +?ChatMessageFlow?Push +Subject?ChatMessageFlow?Chat +??done diff --git a/examples/reticulum_app/components/Chat/configuration/external_action_mapping.ini b/examples/reticulum_app/components/Chat/configuration/external_action_mapping.ini new file mode 100644 index 0000000..bb3d0d6 --- /dev/null +++ b/examples/reticulum_app/components/Chat/configuration/external_action_mapping.ini @@ -0,0 +1,2 @@ +[actionmapping] +??Chat = reticulum_app.components.Chat.Chat_facade.Chat.Chat diff --git a/examples/reticulum_app/components/Chat/configuration/internal_action_mapping.ini b/examples/reticulum_app/components/Chat/configuration/internal_action_mapping.ini new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/configuration/logging.conf b/examples/reticulum_app/components/Chat/configuration/logging.conf new file mode 100644 index 0000000..17168b9 --- /dev/null +++ b/examples/reticulum_app/components/Chat/configuration/logging.conf @@ -0,0 +1,32 @@ +[loggers] +keys=root,Chat_log + +[handlers] +keys=stream_handler,fileHandler + +[formatters] +keys=formatter + +[logger_root] +level=DEBUG +handlers=fileHandler + +[logger_Chat_log] +level=DEBUG +qualname=Chat_log +handlers=fileHandler + +[handler_stream_handler] +class=StreamHandler +level=DEBUG +formatter=formatter +args=(sys.stderr,) + +[handler_fileHandler] +class=FileHandler +level=DEBUG +formatter=formatter +args=('%(logfilename)s',) + +[formatter_formatter] +format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s diff --git a/examples/reticulum_app/components/Chat/configuration/manifest.ini b/examples/reticulum_app/components/Chat/configuration/manifest.ini new file mode 100644 index 0000000..87b70a9 --- /dev/null +++ b/examples/reticulum_app/components/Chat/configuration/manifest.ini @@ -0,0 +1,21 @@ +[ChatManifest] +; universally unique identifier (UUID) Version 1 +UUID = 06fe44ea-af72-415e-bf23-76a63ffa9828 +; Name of this component +name = Chat +; progressive version of the component in major.minor format +version = "24.11.26" +; Aphrodite DigitalPy version this component is build for +requiredAlfaVersion= 0.0.1 +; short description of the component +description = ""Filmology is a test API that describes all the possible variant of a DAF + API model and his implementation as a DigitalPy application"" +; name of the component's author +author=""freeTAKTeam"" +; email of the author +author_email=""Freetakteam@gmail.com"" +; location of the project for the component +url=""https://github.com/FreeTAKTeam"" +; Location of the release of this component +repo = "https://github.com/FreeTAKTeam" +; software license for this component +license = "EPL" diff --git a/examples/reticulum_app/components/Chat/configuration/model_definitions/__init__.py b/examples/reticulum_app/components/Chat/configuration/model_definitions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/controllers/__init__.py b/examples/reticulum_app/components/Chat/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/controllers/chat_controller.py b/examples/reticulum_app/components/Chat/controllers/chat_controller.py new file mode 100644 index 0000000..8d2bf36 --- /dev/null +++ b/examples/reticulum_app/components/Chat/controllers/chat_controller.py @@ -0,0 +1,36 @@ +""" +This is the main controller class of the application. Every operation of the controller is realized by this file +OOTB. It is recommended that you (the developper) avoid adding further methods to the file and instead add supporting +controllers with these methods should you need them. This controller is called directly by the facade in order to +fulfil any requests made to the component by default. +""" + +from typing import TYPE_CHECKING + +from digitalpy.core.main.controller import Controller + +if TYPE_CHECKING: + from digitalpy.core.digipy_configuration.domain.model.configuration import Configuration + from digitalpy.core.zmanager.impl.default_action_mapper import DefaultActionMapper + from digitalpy.core.zmanager.request import Request + from digitalpy.core.zmanager.response import Response + from digitalpy.core.domain.domain.network_client import NetworkClient + +class ChatController(Controller): + + def __init__(self, request: 'Request', + response: 'Response', + sync_action_mapper: 'DefaultActionMapper', + configuration: 'Configuration'): + super().__init__(request, response, sync_action_mapper, configuration) + + def initialize(self, request: 'Request', response: 'Response'): + """This function is used to initialize the controller. + It is intiated by the service manager.""" + return super().initialize(request, response) + + def Chat(self, body: str, client: 'NetworkClient', *args, **kwargs): + """This function is used to handle the Chat action.""" + self.response.set_value("client", None) # ensure the client is set to None so broadcast can be used + self.response.set_value("message", body) + return self.response \ No newline at end of file diff --git a/examples/reticulum_app/components/Chat/controllers/directors/__init__.py b/examples/reticulum_app/components/Chat/controllers/directors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/domain/__init__.py b/examples/reticulum_app/components/Chat/domain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/domain/builder/__init__.py b/examples/reticulum_app/components/Chat/domain/builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/domain/model/__init__.py b/examples/reticulum_app/components/Chat/domain/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/Chat/persistence/__init__.py b/examples/reticulum_app/components/Chat/persistence/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/components/__init__.py b/examples/reticulum_app/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/configuration/application_configuration.ini b/examples/reticulum_app/configuration/application_configuration.ini new file mode 100644 index 0000000..2870be7 --- /dev/null +++ b/examples/reticulum_app/configuration/application_configuration.ini @@ -0,0 +1,30 @@ +; This is a template application configuration file for a DigitalPy application. +; The application configuration will configure object in the ConfigurationFactory. +; These objects will store configuration data and can be used by the application. +; Some configuration objects are used to configure components, and others are used to configure services. +; Below is an example of a service configuration for a simple TCP service. +; [dp_application.simple_tcp] +; __class = digitalpy.core.service_management.domain.model.service_configuration.ServiceConfiguration +; status = STOPPED +; name = SimpleTCPService +; port = 8443 +; host = 0.0.0.0 +; protocol = TCPNetwork + +; The application configuration file must be updated to include the service configuration of any new services. +; For example, the following configuration will add a new service configuration for a simple TCP service. +[ServiceManagementConfiguration] +__class = digitalpy.core.service_management.domain.model.service_management_configuration.ServiceManagementConfiguration +services = [reticulum_app.reticulum] + +[ComponentManagementConfiguration] +component_import_root = reticulum_app.components + +[reticulum_app.reticulum] +__class = digitalpy.core.service_management.domain.model.service_configuration.ServiceConfiguration +status = RUNNING +name = ReticulumService +port = 8443 +host = 0.0.0.0 +protocol = Reticulum +flows = [ChatMessageFlow] \ No newline at end of file diff --git a/examples/reticulum_app/configuration/object_configuration.ini b/examples/reticulum_app/configuration/object_configuration.ini new file mode 100644 index 0000000..aef5b32 --- /dev/null +++ b/examples/reticulum_app/configuration/object_configuration.ini @@ -0,0 +1,20 @@ +; This is the object configuration file for your DigitalPy Application +; You can define your objects here and use them in your application +; via the ObjectFactory. New objects can be added by creating a new +; section and defining the object's properties and class. For example: +; [my_new_object] +; __class = my_app.my_module.MyClass +; named_init_arg1 = value1 +; named_init_arg2 = value2 + +; You can also update the configuration defaults of the digitalpy framework, +; by adding a new section and defining the properties you want to override. +; For example: +;[digitalpy.core_api] +;blueprint_path = NewPath/To/My/blueprints/ +;blueprint_import_base = MyDifferentRoute.blueprints + +[reticulum_app.reticulum] +__class = reticulum_app.services.reticulum_service.ReticulumService +identity_path = .identity +storage_path = .storage \ No newline at end of file diff --git a/examples/reticulum_app/main.py b/examples/reticulum_app/main.py new file mode 100644 index 0000000..8fea326 --- /dev/null +++ b/examples/reticulum_app/main.py @@ -0,0 +1,33 @@ +""" +Main module for a DigitalPy application. + +This script defines the main class for a DigitalPy application, +which serves as the entry point for the application. +""" + +from digitalpy.core.main.DigitalPy import DigitalPy + + +class DigitalPyApp(DigitalPy): + """ + Main class for the DigitalPy application. + + This class inherits from DigitalPy and initializes the application + with the required configuration and setup. + """ + + def __init__(self): + """ + Initialize the DigitalPyApp instance. + + This constructor initializes the base DigitalPy class and + configures the application by calling the _initialize_app_configuration method. + """ + super().__init__() + self._initialize_app_configuration() + +if __name__ == "__main__": + # Entry point for the DigitalPy application. + # + # This block ensures that the application starts only when executed as a script. + DigitalPyApp().start() diff --git a/examples/reticulum_app/services/__init__.py b/examples/reticulum_app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/reticulum_app/services/reticulum_service.py b/examples/reticulum_app/services/reticulum_service.py new file mode 100644 index 0000000..c5ea99a --- /dev/null +++ b/examples/reticulum_app/services/reticulum_service.py @@ -0,0 +1,95 @@ +import importlib +import pathlib +import traceback +import os + +from digitalpy.core.serialization.serialization_facade import Serialization +from digitalpy.core.service_management.domain.model.service_status_enum import ( + ServiceStatusEnum, +) +from digitalpy.core.main.singleton_configuration_factory import ( + SingletonConfigurationFactory, +) +from digitalpy.core.main.impl.configuration_factory import ConfigurationFactory +from digitalpy.core.zmanager.impl.integration_manager_subscriber import ( + IntegrationManagerSubscriber, +) +from digitalpy.core.zmanager.impl.integration_manager_pusher import ( + IntegrationManagerPusher, +) +from digitalpy.core.zmanager.impl.subject_pusher import SubjectPusher +from digitalpy.core.service_management.domain.model.service_configuration import ( + ServiceConfiguration, +) +from digitalpy.core.main.object_factory import ObjectFactory +from digitalpy.core.service_management.digitalpy_service import ( + DigitalPyService, + COMMAND_ACTION, +) +from digitalpy.core.service_management.domain.model.service_status_enum import ( + ServiceStatusEnum, +) +from digitalpy.core.zmanager.request import Request +from digitalpy.core.main.impl.default_factory import DefaultFactory +from digitalpy.core.telemetry.tracing_provider import TracingProvider +from digitalpy.core.zmanager.response import Response + + +CONFIGURATION_SECTION = "reticulum_app.reticulum" + + +class ReticulumService(DigitalPyService): + def __init__( + self, + service: ServiceConfiguration, + integration_manager_subscriber: IntegrationManagerSubscriber, + integration_manager_pusher: IntegrationManagerPusher, + subject_pusher: SubjectPusher, + identity_path: str, + storage_path: str, + ): + super().__init__( + service_id="reticulum_app.reticulum", + service=service, + integration_manager_subscriber=integration_manager_subscriber, + subject_pusher=subject_pusher, + integration_manager_pusher=integration_manager_pusher, + ) + self._identity_path = identity_path + self._storage_path = storage_path + + def handle_inbound_message(self, message): + self.handle_ret_message(message) + + def handle_ret_message(self, message: Request): + message.set_format("pickled") + flow =SingletonConfigurationFactory.get_action_flow("ChatMessageFlow") + message.action_key = flow.actions[0] + self._subject_pusher.push_container(message) + + def serialize(self, message): + return message.get_value("message") + + def handle_response(self, response: Response): + self.serialize(response) + self.protocol.send_response(response) + + def start(self, object_factory: DefaultFactory, tracing_provider: TracingProvider, conf_factory: ConfigurationFactory): + SingletonConfigurationFactory().configure(conf_factory) + ObjectFactory().configure(object_factory) + self.tracer = tracing_provider.create_tracer(self.service_id) + self.initialize_controllers() + self.initialize_connections() + + self.protocol.initialize_network( + None, + None, + self._identity_path, + self._storage_path, + self.configuration, + ) + self.status = ServiceStatusEnum.RUNNING.value + self.execute_main_loop() + + + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 2950102..9545331 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,8 @@ sqlalchemy="^2.0.30" flask_jwt_extended="*" requests="*" psutil="*" +rns = "^0.9.2" +lxmf = "^0.6.2" [tool.poetry.group.dev.dependencies] pytest-cov = "^5.0.0"