More presence service rework

This commit is contained in:
Dan Williams
2006-06-12 18:31:26 -04:00
parent d931dca579
commit 17c371119d
10 changed files with 381 additions and 224 deletions
+125 -37
View File
@@ -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,24 +244,19 @@ 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]
# 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]
except KeyError:
pass
# Remove the service from the buddy
if buddy:
buddy.remove_service(found[0])
# FIXME: send buddy service remove signals
if not buddy.is_valid():
del self._buddies[name]
# FIXME: send buddy disappeared message
for service in found:
self._unresolved_services.remove(service)
return False
@@ -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"))