|
|
|
@ -2,16 +2,40 @@ import threading
|
|
|
|
|
import avahi, dbus, dbus.glib, dbus.dbus_bindings, gobject
|
|
|
|
|
import Buddy
|
|
|
|
|
import Service
|
|
|
|
|
import Group
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ACTION_SERVICE_APPEARED = 'appeared'
|
|
|
|
|
ACTION_SERVICE_DISAPPEARED = 'disappeared'
|
|
|
|
|
def _get_local_ip_address(ifname):
|
|
|
|
|
"""Call Linux specific bits to retrieve our own IP address."""
|
|
|
|
|
import socket
|
|
|
|
|
import sys
|
|
|
|
|
import fcntl
|
|
|
|
|
|
|
|
|
|
class PresenceService(object):
|
|
|
|
|
addr = None
|
|
|
|
|
SIOCGIFADDR = 0x8915
|
|
|
|
|
sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
|
try:
|
|
|
|
|
ifreq = (ifname + '\0'*32)[:32]
|
|
|
|
|
result = fcntl.ioctl(sockfd.fileno(), SIOCGIFADDR, ifreq)
|
|
|
|
|
addr = socket.inet_ntoa(result[20:24])
|
|
|
|
|
except IOError, exc:
|
|
|
|
|
print "Error getting IP address: %s" % exc
|
|
|
|
|
sockfd.close()
|
|
|
|
|
return addr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PresenceService(gobject.GObject):
|
|
|
|
|
"""Object providing information about the presence of Buddies
|
|
|
|
|
and what activities they make available to others."""
|
|
|
|
|
|
|
|
|
|
__gsignals__ = {
|
|
|
|
|
'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
|
|
|
([gobject.TYPE_PYOBJECT])),
|
|
|
|
|
'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
|
|
|
([gobject.TYPE_PYOBJECT]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
__lock = threading.Lock()
|
|
|
|
|
__instance = None
|
|
|
|
|
|
|
|
|
@ -26,12 +50,20 @@ class PresenceService(object):
|
|
|
|
|
get_instance = staticmethod(get_instance)
|
|
|
|
|
|
|
|
|
|
def __init__(self, debug=False):
|
|
|
|
|
gobject.GObject.__init__(self)
|
|
|
|
|
|
|
|
|
|
self._debug = debug
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
self._started = False
|
|
|
|
|
|
|
|
|
|
# interface -> IP address: interfaces we've gotten events on so far
|
|
|
|
|
self._local_addrs = {}
|
|
|
|
|
|
|
|
|
|
# nick -> Buddy: buddies we've found
|
|
|
|
|
self._buddies = {}
|
|
|
|
|
# Our owner object
|
|
|
|
|
self._owner = None
|
|
|
|
|
|
|
|
|
|
# group UID -> Group: groups we've found
|
|
|
|
|
self._groups = {}
|
|
|
|
|
|
|
|
|
@ -77,6 +109,10 @@ class PresenceService(object):
|
|
|
|
|
if self._debug:
|
|
|
|
|
print "PresenceService(%d): %s" % (os.getpid(), msg)
|
|
|
|
|
|
|
|
|
|
def get_owner(self):
|
|
|
|
|
"""Return the owner of this machine/instance, if we've recognized them yet."""
|
|
|
|
|
return self._owner
|
|
|
|
|
|
|
|
|
|
def _resolve_service_error_handler(self, err):
|
|
|
|
|
self._log("error resolving service: %s" % err)
|
|
|
|
|
|
|
|
|
@ -97,6 +133,49 @@ class PresenceService(object):
|
|
|
|
|
found.append(service)
|
|
|
|
|
return found
|
|
|
|
|
|
|
|
|
|
def _is_special_service_type(self, stype):
|
|
|
|
|
"""Return True if the service type is a special, internal service
|
|
|
|
|
type, and False if it's not."""
|
|
|
|
|
if stype == Buddy.PRESENCE_SERVICE_TYPE:
|
|
|
|
|
return True
|
|
|
|
|
if Group.is_group_service_type(stype):
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _handle_new_service_for_buddy(self, service):
|
|
|
|
|
"""Deal with a new discovered service object."""
|
|
|
|
|
# Once a service is resolved, we match it up to an existing buddy,
|
|
|
|
|
# or create a new Buddy if this is the first service known about the buddy
|
|
|
|
|
added = was_valid = False
|
|
|
|
|
name = service.get_name()
|
|
|
|
|
buddy = None
|
|
|
|
|
try:
|
|
|
|
|
buddy = self._buddies[name]
|
|
|
|
|
was_valid = buddy.is_valid()
|
|
|
|
|
added = buddy.add_service(service)
|
|
|
|
|
except KeyError:
|
|
|
|
|
# Should this service mark the owner?
|
|
|
|
|
if service.get_address() in self._local_addrs.values():
|
|
|
|
|
buddy = Buddy.Owner(service)
|
|
|
|
|
self._owner = buddy
|
|
|
|
|
else:
|
|
|
|
|
buddy = Buddy.Buddy(service)
|
|
|
|
|
self._buddies[name] = buddy
|
|
|
|
|
added = True
|
|
|
|
|
if not was_valid and buddy.is_valid():
|
|
|
|
|
self.emit("buddy-appeared", buddy)
|
|
|
|
|
return buddy
|
|
|
|
|
|
|
|
|
|
def _handle_new_service_for_group(self, service, buddy):
|
|
|
|
|
# If the serivce is a group service, merge it into our groups list
|
|
|
|
|
if not buddy:
|
|
|
|
|
return
|
|
|
|
|
group = None
|
|
|
|
|
if not self._groups.has_key(service.get_type()):
|
|
|
|
|
group = Group.Group(service)
|
|
|
|
|
else:
|
|
|
|
|
group = self._groups[service.get_type()]
|
|
|
|
|
|
|
|
|
|
def _resolve_service_reply_cb(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
|
|
|
|
|
"""When the service discovery finally gets here, we've got enough information about the
|
|
|
|
|
service to assign it to a buddy."""
|
|
|
|
@ -108,7 +187,6 @@ class PresenceService(object):
|
|
|
|
|
stype=stype, domain=domain)
|
|
|
|
|
if not len(found):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
for service in found:
|
|
|
|
|
self._unresolved_services.remove(service)
|
|
|
|
|
|
|
|
|
@ -118,23 +196,11 @@ class PresenceService(object):
|
|
|
|
|
service.set_port(port)
|
|
|
|
|
service.set_properties(txt)
|
|
|
|
|
|
|
|
|
|
# Once a service is resolved, we match it up to an existing buddy,
|
|
|
|
|
# or create a new Buddy if this is the first service known about the buddy
|
|
|
|
|
added = was_valid = False
|
|
|
|
|
try:
|
|
|
|
|
buddy = self._buddies[name]
|
|
|
|
|
was_valid = buddy.is_valid()
|
|
|
|
|
added = buddy.add_service(service)
|
|
|
|
|
except KeyError:
|
|
|
|
|
buddy = Buddy.Buddy(service)
|
|
|
|
|
self._buddies[name] = buddy
|
|
|
|
|
added = True
|
|
|
|
|
if not was_valid and buddy.is_valid():
|
|
|
|
|
# FIXME: send out "new buddy" signals
|
|
|
|
|
pass
|
|
|
|
|
if added:
|
|
|
|
|
# FIXME: send out buddy service added signals
|
|
|
|
|
pass
|
|
|
|
|
# Merge the service into our buddy and group lists, if needed
|
|
|
|
|
buddy = self._handle_new_service_for_buddy(service)
|
|
|
|
|
if service.is_group_service():
|
|
|
|
|
self._handle_new_service_for_group(service, buddy)
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _resolve_service_reply_cb_glue(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
|
|
|
|
@ -160,8 +226,16 @@ class PresenceService(object):
|
|
|
|
|
service = Service.Service(name, stype, domain)
|
|
|
|
|
self._unresolved_services.append(service)
|
|
|
|
|
|
|
|
|
|
# Find out the IP address of this interface, if we haven't already
|
|
|
|
|
if interface not in self._local_addrs.keys():
|
|
|
|
|
ifname = self._server.GetNetworkInterfaceNameByIndex(interface)
|
|
|
|
|
if ifname:
|
|
|
|
|
addr = _get_local_ip_address(ifname)
|
|
|
|
|
if addr:
|
|
|
|
|
self._local_addrs[interface] = addr
|
|
|
|
|
|
|
|
|
|
# If we care about the service right now, resolve it
|
|
|
|
|
if stype in self._allowed_service_types or stype == Buddy.PRESENCE_SERVICE_TYPE:
|
|
|
|
|
if stype in self._allowed_service_types or self._is_special_service_type(stype):
|
|
|
|
|
gobject.idle_add(self._resolve_service, interface, protocol, name, stype, domain, flags)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
@ -170,23 +244,18 @@ class PresenceService(object):
|
|
|
|
|
|
|
|
|
|
def _service_disappeared_cb(self, interface, protocol, name, stype, domain, flags):
|
|
|
|
|
self._log("service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (name, stype, domain, interface, protocol))
|
|
|
|
|
# Remove the service from our unresolved services list
|
|
|
|
|
found = self._find_service(self._unresolved_services, name=name,
|
|
|
|
|
stype=stype, domain=domain)
|
|
|
|
|
|
|
|
|
|
buddy = None
|
|
|
|
|
try:
|
|
|
|
|
# Remove the service from the buddy
|
|
|
|
|
buddy = self._buddies[name]
|
|
|
|
|
except KeyError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# Remove the service from the buddy
|
|
|
|
|
if buddy:
|
|
|
|
|
buddy.remove_service(found[0])
|
|
|
|
|
# FIXME: send buddy service remove signals
|
|
|
|
|
# FIXME: need to be more careful about how we remove services
|
|
|
|
|
# from buddies; this could be spoofed
|
|
|
|
|
service = buddy.get_service_of_type(stype)
|
|
|
|
|
buddy.remove_service(service)
|
|
|
|
|
if not buddy.is_valid():
|
|
|
|
|
self.emit("buddy-disappeared", buddy)
|
|
|
|
|
del self._buddies[name]
|
|
|
|
|
# FIXME: send buddy disappeared message
|
|
|
|
|
except KeyError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
for service in found:
|
|
|
|
|
self._unresolved_services.remove(service)
|
|
|
|
@ -246,9 +315,11 @@ class PresenceService(object):
|
|
|
|
|
def track_service_type(self, stype):
|
|
|
|
|
"""Requests that the Presence service look for and recognize
|
|
|
|
|
a certain mDNS service types."""
|
|
|
|
|
if not self._started:
|
|
|
|
|
raise RuntimeError("presence service must be started first.")
|
|
|
|
|
if not type(stype) == type(""):
|
|
|
|
|
raise ValueError("service type must be a string.")
|
|
|
|
|
if stype == Buddy.PRESENCE_SERVICE_TYPE:
|
|
|
|
|
if self._is_special_service_type(stype):
|
|
|
|
|
return
|
|
|
|
|
if stype in self._allowed_service_types:
|
|
|
|
|
return
|
|
|
|
@ -263,6 +334,8 @@ class PresenceService(object):
|
|
|
|
|
|
|
|
|
|
def untrack_service_type(self, stype):
|
|
|
|
|
"""Stop tracking a certain mDNS service."""
|
|
|
|
|
if not self._started:
|
|
|
|
|
raise RuntimeError("presence service must be started first.")
|
|
|
|
|
if not type(stype) == type(""):
|
|
|
|
|
raise ValueError("service type must be a string.")
|
|
|
|
|
if name in self._allowed_service_types:
|
|
|
|
@ -270,19 +343,25 @@ class PresenceService(object):
|
|
|
|
|
|
|
|
|
|
def register_service(self, service):
|
|
|
|
|
"""Register a new service, advertising it to other Buddies on the network."""
|
|
|
|
|
if not self._started:
|
|
|
|
|
raise RuntimeError("presence service must be started first.")
|
|
|
|
|
|
|
|
|
|
rs_name = service.get_name()
|
|
|
|
|
rs_stype = service.get_type()
|
|
|
|
|
rs_port = service.get_port()
|
|
|
|
|
if type(rs_port) != type(1) and rs_port <= 1024:
|
|
|
|
|
raise ValueError("invalid service port.")
|
|
|
|
|
rs_props = service.get_properties()
|
|
|
|
|
rs_domain = service.get_domain()
|
|
|
|
|
if not rs_domain or not len(rs_domain):
|
|
|
|
|
rs_domain = ""
|
|
|
|
|
self._log("registered service name '%s' type '%s' on port %d with args %s" % (rs_name, rs_stype, rs_port, rs_props))
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
group = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, self._server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP)
|
|
|
|
|
info = ["%s=%s" % (k, v) for k, v in rs_props.items()]
|
|
|
|
|
group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, rs_name, rs_stype,
|
|
|
|
|
"", "", # domain, host (let the system figure it out)
|
|
|
|
|
rs_domain, "", # let Avahi figure the 'host' out
|
|
|
|
|
dbus.UInt16(rs_port), info,)
|
|
|
|
|
group.Commit()
|
|
|
|
|
except dbus.dbus_bindings.DBusException, exc:
|
|
|
|
@ -295,11 +374,19 @@ class PresenceService(object):
|
|
|
|
|
return group
|
|
|
|
|
|
|
|
|
|
def get_buddy_by_nick_name(self, nick_name):
|
|
|
|
|
"""Look up and return a buddy by nickname."""
|
|
|
|
|
if self._buddies.has_key(nick_name):
|
|
|
|
|
return self._buddies[nick_name]
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_buddy_by_address(self, address):
|
|
|
|
|
for buddy in self._buddies.values():
|
|
|
|
|
if buddy.get_address == address:
|
|
|
|
|
return buddy
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_buddies(self):
|
|
|
|
|
"""Return the entire buddy list."""
|
|
|
|
|
return self._buddies.values()
|
|
|
|
|
|
|
|
|
|
#################################################################
|
|
|
|
@ -347,6 +434,7 @@ class PresenceServiceTestCase(unittest.TestCase):
|
|
|
|
|
buddy = ps.get_buddy_by_nick_name("Paul")
|
|
|
|
|
assert buddy, "The registered buddy was not found after 2 seconds!"
|
|
|
|
|
assert buddy.is_valid(), "The buddy was invalid, since no presence was advertised."
|
|
|
|
|
assert buddy.is_owner() == True, "The buddy was not the owner, but it should be!"
|
|
|
|
|
|
|
|
|
|
def addToSuite(suite):
|
|
|
|
|
suite.addTest(PresenceServiceTestCase("testNoServices"))
|
|
|
|
|