1 """
2 Contains classes for advertising conduit via avahi and for transmitting and
3 receiving python objects over the network.
4
5 Parts of this code adapted from glchess (GPLv2)
6 http://glchess.sourceforge.net/
7 Parts of this code adapted from elisa (GPLv2)
8 Parts of this code adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/457669
9
10 Copyright: John Stowers, 2006
11 License: GPLv2
12 """
13
14 import dbus.glib
15 import logging
16 log = logging.getLogger("modules.Network")
17
18 import conduit
19
20 AVAHI_SERVICE_NAME = "_conduit._tcp"
21 AVAHI_SERVICE_DOMAIN = ""
22 PROTOCOL_VERSION = "1"
23
24 PORT_IDX = 0
25 VERSION_IDX = 1
26
27
28
29
30
31 DBUS_INTERFACE_ADDRESS_RESOLVER = 'org.freedesktop.Avahi.AddressResolver'
32 DBUS_INTERFACE_DOMAIN_BROWSER = 'org.freedesktop.Avahi.DomainBrowser'
33 DBUS_INTERFACE_ENTRY_GROUP = 'org.freedesktop.Avahi.EntryGroup'
34 DBUS_INTERFACE_HOST_NAME_RESOLVER = 'org.freedesktop.Avahi.HostNameResolver'
35 DBUS_INTERFACE_RECORD_BROWSER = 'org.freedesktop.Avahi.RecordBrowser'
36 DBUS_INTERFACE_SERVER = 'org.freedesktop.Avahi.Server'
37 DBUS_INTERFACE_SERVICE_BROWSER = 'org.freedesktop.Avahi.ServiceBrowser'
38 DBUS_INTERFACE_SERVICE_RESOLVER = 'org.freedesktop.Avahi.ServiceResolver'
39 DBUS_INTERFACE_SERVICE_TYPE_BROWSER = 'org.freedesktop.Avahi.ServiceTypeBrowser'
40 DBUS_NAME = 'org.freedesktop.Avahi'
41 DBUS_PATH_SERVER = '/'
42 DOMAIN_BROWSER_BROWSE = 0
43 DOMAIN_BROWSER_BROWSE_DEFAULT = 1
44 DOMAIN_BROWSER_BROWSE_LEGACY = 4
45 DOMAIN_BROWSER_REGISTER = 2
46 DOMAIN_BROWSER_REGISTER_DEFAULT = 3
47 ENTRY_GROUP_COLLISION = 3
48 ENTRY_GROUP_ESTABLISHED = 2
49 ENTRY_GROUP_FAILURE = 4
50 ENTRY_GROUP_REGISTERING = 1
51 ENTRY_GROUP_UNCOMMITED = 0
52 IF_UNSPEC = -1
53 LOOKUP_NO_ADDRESS = 8
54 LOOKUP_NO_TXT = 4
55 LOOKUP_RESULT_CACHED = 1
56 LOOKUP_RESULT_LOCAL = 8
57 LOOKUP_RESULT_MULTICAST = 4
58 LOOKUP_RESULT_OUR_OWN = 16
59 LOOKUP_RESULT_STATIC = 32
60 LOOKUP_RESULT_WIDE_AREA = 2
61 LOOKUP_USE_MULTICAST = 2
62 LOOKUP_USE_WIDE_AREA = 1
63 PROTO_INET = 0
64 PROTO_INET6 = 1
65 PROTO_UNSPEC = -1
66 PUBLISH_ALLOW_MULTIPLE = 8
67 PUBLISH_NO_ANNOUNCE = 4
68 PUBLISH_NO_COOKIE = 32
69 PUBLISH_NO_PROBE = 2
70 PUBLISH_NO_REVERSE = 16
71 PUBLISH_UNIQUE = 1
72 PUBLISH_UPDATE = 64
73 PUBLISH_USE_MULTICAST = 256
74 PUBLISH_USE_WIDE_AREA = 128
75 SERVER_COLLISION = 3
76 SERVER_FAILURE = 4
77 SERVER_INVALID = 0
78 SERVER_REGISTERING = 1
79 SERVER_RUNNING = 2
80 SERVICE_COOKIE = 'org.freedesktop.Avahi.cookie'
81 SERVICE_COOKIE_INVALID = 0
82
84 r = ""
85 for c in s:
86 if c >= 32 and c < 127:
87 r += "%c" % c
88 else:
89 r += "."
90 return r
91
97
98
100 r = []
101 for c in s:
102 r.append(dbus.Byte(ord(c)))
103 return r
104
110
112 l = []
113 for k,v in txt_dict.items():
114 l.append(string_to_byte_array("%s=%s" % (k,v)))
115 return l
116
118 d = {}
119 for i in array:
120 bits = i.split("=")
121 if len(bits) == 2:
122 d[bits[0]] = bits[1]
123 return d
124
126 """
127 Advertises the presence of dataprovider instances on the network using avahi.
128 Wraps up some of the complexity due to it being hard to add additional
129 services to a group once that group has been committed.
130 """
154
156 """
157 Resets the group, announces Conduit, and commits the change
158 """
159 log.debug("Announcing avahi conduit service")
160 self.group.AddService(
161 IF_UNSPEC,
162 PROTO_UNSPEC,
163 dbus.UInt32(0),
164 self.hostname,
165 AVAHI_SERVICE_NAME,
166 AVAHI_SERVICE_DOMAIN,
167 '',
168 dbus.UInt16(self.port),
169 string_array_to_txt_array([
170 "version=%s" % conduit.VERSION,
171 "protocol-version=%s" % PROTOCOL_VERSION])
172 )
173 self.group.Commit()
174
176 if not self.group.IsEmpty():
177 self.group.Reset()
178
180 """
181 Watches the network for other conduit instances using avahi.
182
183 Code adapted from elisa
184 """
185 - def __init__(self, dataprovider_detected_cb, dataprovider_removed_cb):
186 """
187 Connects to the system bus and configures avahi to listen for
188 Conduit services
189 """
190
191 self.detected_cb = dataprovider_detected_cb
192 self.removed_cb = dataprovider_removed_cb
193
194 bus = dbus.SystemBus()
195 self.server = dbus.Interface(
196 bus.get_object(
197 DBUS_NAME,
198 DBUS_PATH_SERVER),
199 DBUS_INTERFACE_SERVER)
200
201 self.hostname = self.server.GetHostName()
202
203 obj = bus.get_object(
204 DBUS_NAME,
205 self.server.ServiceBrowserNew(
206 IF_UNSPEC,
207 PROTO_UNSPEC,
208 AVAHI_SERVICE_NAME,
209 AVAHI_SERVICE_DOMAIN,
210 dbus.UInt32(0)
211 )
212 )
213 browser = dbus.Interface(obj, DBUS_INTERFACE_SERVICE_BROWSER)
214 browser.connect_to_signal('ItemNew', self._new_service)
215 browser.connect_to_signal('ItemRemove', self._remove_service)
216
217 - def _new_service(self, interface, protocol, name, type, domain, flags):
237
238 - def _resolve_service(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
239 """
240 Dbus callback
241 """
242 extra_info = txt_array_to_string_array(txt)
243 extra = txt_array_to_dict(extra_info)
244
245 log.debug("Resolved conduit service %s on %s - %s:%s\nExtra Info: %s" % (name, host, address, port, extra_info))
246
247
248
249 if extra.get("protocol-version", None) == PROTOCOL_VERSION:
250 self.detected_cb(str(name), str(host), str(address), str(port), extra_info)
251 else:
252 log.debug("Ignoring %s (version: %s, protocol version: %s)" % (
253 name,
254 extra.get("version", "unknown"),
255 extra.get("protocol-version", "unknown")))
256
257 - def _remove_service(self, interface, protocol, name, type, domain, flags):
258 """
259 Dbus callback when a service is removed
260 """
261 self.removed_cb(str(name))
262
264 """
265 Dbus callback when a service details cannot be resolved
266 """
267 log.warn("Avahi/D-Bus error: %s" % repr(error))
268