1"""Node
2
3this module manage node (start server, add peer, ...)
4.. seealso:: Examples in :dir:`examples.node`
5"""
6
7from .client import Client
8from .events import connected_to, disconnected_from, remote
9from .server import Server
10
11from circuits import BaseComponent, handler, Timer
12from circuits.net.events import connect
13
14
15class Node(BaseComponent):
16
17    """Node
18
19    this class manage node (start server, add peer, ...)
20    .. seealso:: Examples in :dir:`examples.node`
21    """
22    channel = 'node'
23    __peers = {}
24
25    def __init__(self, port=None, channel=channel, **kwargs):
26        """Start node system.
27
28        :param port:    An optional keyword argument which if defined,
29                        start server on this port.
30                        **Default:** ``None`` (don't start the server)
31        :type port:     int
32
33        :param server_ip:   An optional keyword argument which define
34                            ip where the socket has listen to.
35                            **Default:** ``0.0.0.0`` (all ip is allowed)
36        :type server_ip:     str
37
38        :param channel: An optional keyword argument which if defined,
39                        set channel used for node event. **Default:** ``node``
40        :type channel:  str
41
42        :param receive_event_firewall:  An optional keyword argument which if
43                                        defined, set function or method to call
44                                        to check if event is allowed for sending.
45                                        **Default:** ``None`` (no firewall)
46        :type receive_event_firewall:   function
47        :type receive_event_firewall:   method
48
49        :param send_event_firewall:  An optional keyword argument which if
50                                    defined, set function or method to call to
51                                    check if event is allowed for executing
52                                    **Default:** ``None`` (no firewall)
53        :type send_event_firewall:   function
54        :type send_event_firewall:   method
55        """
56        super(Node, self).__init__(channel=channel, **kwargs)
57
58        if port is not None:
59            self.server = Server(
60                port, channel=channel, **kwargs).register(self)
61        else:
62            self.server = None
63
64    def add(self, connection_name, hostname, port, **kwargs):
65        """Add new peer to the node.
66
67        :param connection_name:    Connection name.
68        :type connection_name:     str
69
70        :param hostname:    hostname of the remote node.
71        :type hostname:     str
72
73        :param port:    port of the remote node.
74        :type port:     int
75
76        :param auto_remote_event:   An optional keyword argument which if
77                                    defined, bind events automatically to remote
78                                    execution. **Default:** ``{}`` (no events)
79        :type auto_remote_event:    dict
80
81        :param channel: An optional keyword argument which if defined,
82                        set channel used for client event. If this keyword is
83                        not defined the method will generate the channel name
84                        automatically.
85        :type channel:  str
86
87        :param reconnect_delay: An optional keyword argument which if defined,
88                                set auto reconnect delay.
89                                **Default:** ``10`` (seconde)
90        :type reconnect_delay:  int
91
92        :param receive_event_firewall:  An optional keyword argument which if
93                                        defined, function or method to call for
94                                        check if event is allowed for sending.
95                                        **Default:** ``None`` (no firewall)
96        :type receive_event_firewall:   function
97        :type receive_event_firewall:   method
98
99        :param send_event_firewall:  An optional keyword argument which if
100                                    defined, setfunction or method to call to
101                                    check if event is allowed for executing
102                                    **Default:** ``None`` (no firewall)
103        :type send_event_firewall:   function
104        :type send_event_firewall:   method
105
106        :return: Channel used on client event.
107        :rtype: str
108        """
109        # automatic send event to peer
110        auto_remote_event = kwargs.pop('auto_remote_event', {})
111        for event_name in auto_remote_event:
112            for channel in auto_remote_event[event_name]:
113                @handler(event_name, channel=channel)
114                def event_handle(self, event, *args, **kwargs):
115                    yield self.call(remote(event, connection_name))
116            self.addHandler(event_handle)
117
118        client_channel = kwargs.pop(
119            'channel',
120            '%s_client_%s' % (self.channel, connection_name)
121        )
122        reconnect_delay = kwargs.pop('reconnect_delay', 10)
123        client = Client(hostname, port, channel=client_channel, **kwargs)
124
125        # connected event binding
126        @handler('connected', channel=client_channel)
127        def connected(self, hostname, port):
128            self.fire(connected_to(
129                connection_name, hostname, port, client_channel, client
130            ))
131        self.addHandler(connected)
132
133        # disconnected event binding
134        @handler('disconnected', 'unreachable', channel=client_channel)
135        def disconnected(self, event, *args, **kwargs):
136            if event.name == 'disconnected':
137                self.fire(disconnected_from(
138                    connection_name, hostname, port, client_channel, client
139                ))
140
141            # auto reconnect
142            if reconnect_delay > 0:
143                Timer(
144                    reconnect_delay,
145                    connect(hostname, port),
146                    client_channel
147                ).register(self)
148
149        self.addHandler(disconnected)
150
151        client.register(self)
152        self.__peers[connection_name] = client
153        return client_channel
154
155    def get_connection_names(self):
156        """Get connections names
157
158        :return: The list of connections names
159        :rtype: list of str
160        """
161        return list(self.__peers)
162
163    def get_peer(self, connection_name):
164        """Get a client object by name
165
166        :param connection_name:    Connection name.
167        :type connection_name:     str
168
169        :return: The Client object
170        :rtype: :class:`circuits.node.client.Client`
171        """
172        return self.__peers[connection_name] if connection_name in self.__peers\
173            else None
174
175    @handler('remote', channel='*')
176    def __on_remote(self, event, remote_event, connection_name, channel=None):
177        """Send event to peer
178
179        Event handler to run an event on peer (the event definition is
180        :class:`circuits.node.events.remote`)
181
182        :param event:    The event triggered (by the handler)
183        :type event:     :class:`circuits.node.events.remote`
184
185        :param remote_event:    Event to execute remotely.
186        :type remote_event:     :class:`circuits.core.events.Event`
187
188        :param connection_name:    Connection name (peer selection).
189        :type connection_name:     str
190
191        :param channel:    Remote channel (channel to use on peer).
192        :type channel:     str
193
194        :return: The result of remote event
195        :rtype: generator
196
197        :Example:
198        ``# hello is your event to execute remotely
199        # peer_test is peer name
200        result = yield self.fire(remote(hello())), 'peer_test')
201        print(result.value)``
202        """
203        node = self.__peers[connection_name]
204        remote_event.channels = (channel,) if channel is not None \
205            else event.channels
206        return node.send(remote_event)
207