Rationalized output udp forwarding:
[ais.git] / bin / inputs / outpeer.py
1 # -*- coding: utf-8 -*-
2 '''
3 UDP out peers module
4 '''
5
6 __all__ = [ 'OutPeers' ]
7
8
9 import logging
10 from time import time as get_timestamp
11 import socket
12
13 class OutPeer:
14     '''
15     UDP output feed.
16     '''
17     def __init__(self, str_addrport):
18         colpos = str_addrport.rfind(':')
19         if colpos == 1:
20             assert colpos != 1, \
21                 'Missing column in host:port string for %s' % str_addrport
22         self.host = str_addrport[:colpos]
23         if self.host.startswith('['):
24             assert self.host[-1] == ']'
25             self.host = self.host[1:-1]
26         self.port = int(str_addrport[colpos+1:])
27         self.possibles_addrinfo = None
28         self.sock = None
29         self.addr = None
30         self.timestamp_getaddrinfo = 0
31         self._reinit_socket()
32
33
34     def _reinit_socket(self):
35         '''
36         Remove last known addrinfo for that host:port, if any.
37         If addrinfo is empty, fetch a new one.
38         Initialise self.sock and self.addr
39         '''
40         while True:
41             if self.possibles_addrinfo:
42                 # remove current (0th) addrinfo
43                 del self.possibles_addrinfo[0]
44
45             if not self.possibles_addrinfo:
46                 timestamp = get_timestamp()
47                 if timestamp > self.timestamp_getaddrinfo + 60:
48                     try:
49                         logging.debug('Calling socket.getaddrinfo for %s:%s',
50                             self.host, self.port)
51                         self.possibles_addrinfo = socket.getaddrinfo(self.host,
52                             self.port, socket.AF_UNSPEC, socket.SOCK_DGRAM)
53                         logging.info('%s possibles addrinfo for %s:%s.', 
54                             len(self.possibles_addrinfo), self.host, self.port)
55                         logging.debug('possibles_addrinfo: %s ',
56                             self.possibles_addrinfo)
57                     except socket.gaierror, err:
58                         logging.error("Can't resolve %s:%s: %s."
59                             "Will retry in a minute.",
60                             self.host, self.port, err)
61                         return False
62                     finally:
63                         self.timestamp_getaddrinfo = timestamp
64                     if not self.possibles_addrinfo:
65                         logging.error("Can't resolve %s:%s."
66                             "Will retry in a minute.",
67                             self.host, self.port)
68                         return False
69                 else:
70                     return False # waiting 
71
72             addrinfo = self.possibles_addrinfo[0]
73             logging.info('Prepared socket to %s', addrinfo)
74             af, socktype, proto = addrinfo[:3]
75             self.addr = addrinfo[4]
76             try:
77                 self.sock = socket.socket(af, socktype, proto)
78                 return True
79             except socket.error, msg:
80                 logging.error("Can't open socket to %s: %s", self.addr, msg)
81         
82     def send_line(self, line):
83         '''
84         Sends a AIVDM line to that peer.
85         CRLF will be added automatically.
86         '''
87         while self.sock or self._reinit_socket():
88             try:
89                 self.sock.sendto(line+'\r\n', self.addr)
90                 return
91             except socket.error, err:
92                 logging.error('socket.sendto(%s) failed: %s', self.addr, err)
93                 self.sock = None
94
95
96 class OutPeers:
97     '''
98     A group of UDP peers for output.
99     '''
100     def __init__(self, list_from_optparse):
101         '''
102         Just give the list for strings from optparse.
103         '''
104         self.peers = {}
105         self.sock = None
106         for str_addrport in list_from_optparse:
107             peer = OutPeer(str_addrport)
108             self.peers[peer] = 0
109
110     def send_line(self, line):
111         '''
112         Send a line to all the output peers registered.
113         '''
114         logging.debug('OUT %s', repr(line+'\r\n'))
115         for outpeer, stats in self.peers.iteritems():
116             #logging.debug('OUT %s:%s %s',
117             #              formataddr(outpeer[0]), outpeer[1],
118             #              repr(line+'\r\n'))
119             outpeer.send_line(line)
120             self.peers[outpeer] += 1