Package common :: Package zeroconf :: Module zeroconf_bonjour
[hide private]
[frames] | no frames]

Source Code for Module common.zeroconf.zeroconf_bonjour

  1  ##      common/zeroconf/zeroconf_bonjour.py 
  2  ## 
  3  ## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de> 
  4  ## 
  5  ## This file is part of Gajim. 
  6  ## 
  7  ## Gajim is free software; you can redistribute it and/or modify 
  8  ## it under the terms of the GNU General Public License as published 
  9  ## by the Free Software Foundation; version 3 only. 
 10  ## 
 11  ## Gajim is distributed in the hope that it will be useful, 
 12  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 14  ## GNU General Public License for more details. 
 15  ## 
 16  ## You should have received a copy of the GNU General Public License 
 17  ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. 
 18  ## 
 19   
 20  from common import gajim 
 21  import select 
 22  import re 
 23  from common.zeroconf.zeroconf import C_BARE_NAME, C_DOMAIN 
 24   
 25  try: 
 26      import pybonjour 
 27  except ImportError, e: 
 28      pass 
 29   
 30   
 31  resolve_timeout = 1 
 32   
33 -class Zeroconf:
34 - def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, 35 disconnected_CB, error_CB, name, host, port):
36 self.domain = None # specific domain to browse 37 self.stype = '_presence._tcp' 38 self.port = port # listening port that gets announced 39 self.username = name 40 self.host = host 41 self.txt = pybonjour.TXTRecord() # service data 42 43 # XXX these CBs should be set to None when we destroy the object 44 # (go offline), because they create a circular reference 45 self.new_serviceCB = new_serviceCB 46 self.remove_serviceCB = remove_serviceCB 47 self.name_conflictCB = name_conflictCB 48 self.disconnected_CB = disconnected_CB 49 self.error_CB = error_CB 50 51 self.contacts = {} # all current local contacts with data 52 self.connected = False 53 self.announced = False 54 self.invalid_self_contact = {} 55 self.resolved = []
56 57
58 - def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain):
59 gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype)) 60 if not self.connected: 61 return 62 if errorCode != pybonjour.kDNSServiceErr_NoError: 63 return 64 if not (flags & pybonjour.kDNSServiceFlagsAdd): 65 self.remove_service_callback(serviceName) 66 return 67 68 # asynchronous resolving 69 resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback) 70 71 try: 72 while not self.resolved: 73 ready = select.select([resolve_sdRef], [], [], resolve_timeout) 74 if resolve_sdRef not in ready[0]: 75 gajim.log.debug('Resolve timed out') 76 break 77 pybonjour.DNSServiceProcessResult(resolve_sdRef) 78 else: 79 self.resolved.pop() 80 finally: 81 resolve_sdRef.close()
82
83 - def remove_service_callback(self, name):
84 gajim.log.debug('Service %s disappeared.' % name) 85 if not self.connected: 86 return 87 if name != self.name: 88 for key in self.contacts.keys(): 89 if self.contacts[key][C_BARE_NAME] == name: 90 del self.contacts[key] 91 self.remove_serviceCB(key) 92 return
93
94 - def new_domain_callback(self, interface, protocol, domain, flags):
95 if domain != "local": 96 self.browse_domain(interface, protocol, domain)
97 98 # takes a TXTRecord instance
99 - def txt_array_to_dict(self, txt):
100 items = pybonjour.TXTRecord.parse(txt)._items 101 return dict((v[0], v[1]) for v in items.values())
102
103 - def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, 104 hosttarget, port, txtRecord):
105 106 # TODO: do proper decoding... 107 escaping= { 108 r'\.': '.', 109 r'\032': ' ', 110 r'\064': '@', 111 } 112 113 # Split on '.' but do not split on '\.' 114 result = re.split('(?<!\\\\)\.', fullname) 115 name = result[0] 116 protocol, domain = result[2:4] 117 118 # Replace the escaped values 119 for src, trg in escaping.items(): 120 name = name.replace(src, trg) 121 122 txt = pybonjour.TXTRecord.parse(txtRecord) 123 124 gajim.log.debug('Service data for service %s on %i:' % (fullname, interfaceIndex)) 125 gajim.log.debug('Host %s, port %i, TXT data: %s' % (hosttarget, port, txt._items)) 126 127 if not self.connected: 128 return 129 130 bare_name = name 131 if '@' not in name: 132 name = name + '@' + name 133 134 # we don't want to see ourselves in the list 135 if name != self.name: 136 self.contacts[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord) 137 138 self.new_serviceCB(name) 139 else: 140 # remember data 141 # In case this is not our own record but of another 142 # gajim instance on the same machine, 143 # it will be used when we get a new name. 144 self.invalid_self_contact[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord) 145 # count services 146 self.resolved.append(True)
147 148 # different handler when resolving all contacts
149 - def service_resolved_all_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord):
150 if not self.connected: 151 return 152 153 escaping= { 154 r'\.': '.', 155 r'\032': ' ', 156 r'\064': '@', 157 } 158 159 name, stype, protocol, domain, dummy = fullname.split('.') 160 161 # Replace the escaped values 162 for src, trg in escaping.items(): 163 name = name.replace(src, trg) 164 165 bare_name = name 166 if name.find('@') == -1: 167 name = name + '@' + name 168 169 # we don't want to see ourselves in the list 170 if name != self.name: 171 self.contacts[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord)
172 173
174 - def service_added_callback(self, sdRef, flags, errorCode, name, regtype, domain):
175 if errorCode == pybonjour.kDNSServiceErr_NoError: 176 gajim.log.debug('Service successfully added')
177
178 - def service_add_fail_callback(self, err):
179 if err[0][0] == pybonjour.kDNSServiceErr_NameConflict: 180 gajim.log.debug('Error while adding service. %s' % str(err)) 181 parts = self.username.split(' ') 182 183 #check if last part is a number and if, increment it 184 try: 185 stripped = str(int(parts[-1])) 186 except Exception: 187 stripped = 1 188 alternative_name = self.username + str(stripped+1) 189 self.name_conflictCB(alternative_name) 190 return 191 self.error_CB(_('Error while adding service. %s') % str(err)) 192 self.disconnect()
193 194 # make zeroconf-valid names
195 - def replace_show(self, show):
196 if show in ['chat', 'online', '']: 197 return 'avail' 198 elif show == 'xa': 199 return 'away' 200 return show
201
202 - def create_service(self):
203 txt = {} 204 205 #remove empty keys 206 for key, val in self.txt: 207 if val: 208 txt[key] = val 209 210 txt['port.p2pj'] = self.port 211 txt['version'] = 1 212 txt['txtvers'] = 1 213 214 # replace gajim's show messages with compatible ones 215 if 'status' in self.txt: 216 txt['status'] = self.replace_show(self.txt['status']) 217 else: 218 txt['status'] = 'avail' 219 220 self.txt = pybonjour.TXTRecord(txt, strict=True) 221 222 try: 223 sdRef = pybonjour.DNSServiceRegister(name = self.name, 224 regtype = self.stype, port = self.port, txtRecord = self.txt, 225 callBack = self.service_added_callback) 226 self.service_sdRef = sdRef 227 except pybonjour.BonjourError, e: 228 self.service_add_fail_callback(e) 229 else: 230 gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype)) 231 232 ready = select.select([sdRef], [], [], resolve_timeout) 233 if sdRef in ready[0]: 234 pybonjour.DNSServiceProcessResult(sdRef)
235
236 - def announce(self):
237 if not self.connected: 238 return False 239 240 self.create_service() 241 self.announced = True 242 return True
243
244 - def remove_announce(self):
245 if not self.announced: 246 return False 247 try: 248 self.service_sdRef.close() 249 self.announced = False 250 return True 251 except pybonjour.BonjourError, e: 252 gajim.log.debug(e) 253 return False
254 255
256 - def connect(self):
257 self.name = self.username + '@' + self.host # service name 258 259 self.connected = True 260 261 # start browsing 262 if self.domain is None: 263 # Explicitly browse .local 264 self.browse_domain() 265 266 # Browse for other browsable domains 267 #self.domain_sdRef = pybonjour.DNSServiceEnumerateDomains(flags, interfaceIndex=0, callBack=self.new_domain_callback) 268 269 else: 270 self.browse_domain(self.domain) 271 272 return True
273
274 - def disconnect(self):
275 if self.connected: 276 self.connected = False 277 if hasattr(self, 'browse_sdRef'): 278 self.browse_sdRef.close() 279 self.remove_announce()
280
281 - def browse_domain(self, domain=None):
282 gajim.log.debug('starting to browse') 283 try: 284 self.browse_sdRef = pybonjour.DNSServiceBrowse(regtype=self.stype, domain=domain, callBack=self.browse_callback) 285 except pybonjour.BonjourError, e: 286 self.error_CB("Error while browsing: %s" % e)
287
288 - def browse_loop(self):
289 ready = select.select([self.browse_sdRef], [], [], 2) 290 if self.browse_sdRef in ready[0]: 291 pybonjour.DNSServiceProcessResult(self.browse_sdRef)
292 293 # refresh txt data of all contacts manually (no callback available)
294 - def resolve_all(self):
295 if not self.connected: 296 return 297 298 # for now put here as this is synchronous 299 self.browse_loop() 300 301 for val in self.contacts.values(): 302 resolve_sdRef = pybonjour.DNSServiceResolve(0, 303 pybonjour.kDNSServiceInterfaceIndexAny, val[C_BARE_NAME], 304 self.stype + '.', val[C_DOMAIN] + '.', 305 self.service_resolved_all_callback) 306 307 try: 308 ready = select.select([resolve_sdRef], [], [], resolve_timeout) 309 if resolve_sdRef not in ready[0]: 310 gajim.log.debug('Resolve timed out (in resolve_all)') 311 break 312 pybonjour.DNSServiceProcessResult(resolve_sdRef) 313 finally: 314 resolve_sdRef.close()
315
316 - def get_contacts(self):
317 return self.contacts
318
319 - def get_contact(self, jid):
320 if not jid in self.contacts: 321 return None 322 return self.contacts[jid]
323
324 - def update_txt(self, show = None):
325 if show: 326 self.txt['status'] = self.replace_show(show) 327 328 try: 329 pybonjour.DNSServiceUpdateRecord(self.service_sdRef, None, 0, self.txt) 330 except pybonjour.BonjourError: 331 return False 332 return True
333