Cut over to new PresenceService

This commit is contained in:
Dan Williams 2007-04-09 14:40:50 -04:00
parent 2f2f756939
commit 2509d990e4
21 changed files with 25 additions and 2534 deletions

View File

@ -1,176 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import dbus
PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp"
ACTIVITY_DBUS_OBJECT_PATH = "/org/laptop/Presence/Activities/"
ACTIVITY_DBUS_INTERFACE = "org.laptop.Presence.Activity"
class ActivityDBusHelper(dbus.service.Object):
def __init__(self, parent, bus_name, object_path):
self._parent = parent
self._bus_name = bus_name
self._object_path = object_path
dbus.service.Object.__init__(self, bus_name, self._object_path)
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
in_signature="s", out_signature="ao")
def getServicesOfType(self, stype):
ret = []
for serv in self._parent.get_services_of_type(stype):
ret.append(serv.object_path())
return ret
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
in_signature="", out_signature="ao")
def getServices(self):
ret = []
for serv in self._parent.get_services():
ret.append(serv.object_path())
return ret
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
in_signature="", out_signature="s")
def getId(self):
return self._parent.get_id()
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
in_signature="", out_signature="s")
def getColor(self):
return self._parent.get_color()
@dbus.service.method(ACTIVITY_DBUS_INTERFACE,
in_signature="", out_signature="ao")
def getJoinedBuddies(self):
ret = []
for buddy in self._parent.get_joined_buddies():
ret.append(buddy.object_path())
return ret
@dbus.service.signal(ACTIVITY_DBUS_INTERFACE,
signature="o")
def ServiceAppeared(self, object_path):
pass
@dbus.service.signal(ACTIVITY_DBUS_INTERFACE,
signature="o")
def ServiceDisappeared(self, object_path):
pass
@dbus.service.signal(ACTIVITY_DBUS_INTERFACE,
signature="o")
def BuddyJoined(self, object_path):
pass
@dbus.service.signal(ACTIVITY_DBUS_INTERFACE,
signature="o")
def BuddyLeft(self, object_path):
pass
class Activity(object):
def __init__(self, bus_name, object_id, initial_service):
if not initial_service.get_activity_id():
raise ValueError("Service must have a valid Activity ID")
self._activity_id = initial_service.get_activity_id()
self._buddies = []
self._services = {} # service type -> list of Services
self._color = None
self._valid = False
self._object_id = object_id
self._object_path = "/org/laptop/Presence/Activities/%d" % self._object_id
self._dbus_helper = ActivityDBusHelper(self, bus_name, self._object_path)
self.add_service(initial_service)
def object_path(self):
return dbus.ObjectPath(self._object_path)
def is_valid(self):
"""An activity is only valid when it's color is available."""
return self._valid
def get_id(self):
return self._activity_id
def get_color(self):
return self._color
def get_services(self):
ret = []
for serv_list in self._services.values():
for service in serv_list:
if service not in ret:
ret.append(service)
return ret
def get_services_of_type(self, stype):
if self._services.has_key(stype):
return self._services[stype]
return []
def get_joined_buddies(self):
buddies = []
for serv_list in self._services.values():
for serv in serv_list:
owner = serv.get_owner()
if owner and not owner in buddies and owner.is_valid():
buddies.append(owner)
return buddies
def add_service(self, service):
stype = service.get_type()
if not self._services.has_key(stype):
self._services[stype] = []
if not self._color:
color = service.get_one_property('color')
if color:
self._color = color
self._valid = True
# Send out the BuddyJoined signal if this is the first
# service from the buddy that we've seen
buddies = self.get_joined_buddies()
serv_owner = service.get_owner()
if serv_owner and serv_owner not in buddies and serv_owner.is_valid():
self._dbus_helper.BuddyJoined(serv_owner.object_path())
serv_owner.add_activity(self)
if not service in self._services[stype]:
self._services[stype].append(service)
self._dbus_helper.ServiceAppeared(service.object_path())
def remove_service(self, service):
stype = service.get_type()
if not self._services.has_key(stype):
return
self._services[stype].remove(service)
self._dbus_helper.ServiceDisappeared(service.object_path())
if len(self._services[stype]) == 0:
del self._services[stype]
# Send out the BuddyLeft signal if this is the last
# service from the buddy
buddies = self.get_joined_buddies()
serv_owner = service.get_owner()
if serv_owner and serv_owner not in buddies and serv_owner.is_valid():
serv_owner.remove_activity(self)
self._dbus_helper.BuddyLeft(serv_owner.object_path())

View File

@ -1,533 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import base64
import logging
import gobject
import dbus, dbus.service
from sugar import profile
from sugar.graphics import xocolor
PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp"
BUDDY_DBUS_OBJECT_PATH = "/org/laptop/Presence/Buddies/"
BUDDY_DBUS_INTERFACE = "org.laptop.Presence.Buddy"
_BUDDY_KEY_COLOR = 'color'
_BUDDY_KEY_CURACT = 'curact'
class NotFoundError(Exception):
pass
class BuddyDBusHelper(dbus.service.Object):
def __init__(self, parent, bus_name, object_path):
self._parent = parent
self._bus_name = bus_name
self._object_path = object_path
dbus.service.Object.__init__(self, bus_name, self._object_path)
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
signature="o")
def ServiceAppeared(self, object_path):
pass
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
signature="o")
def ServiceDisappeared(self, object_path):
pass
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
signature="")
def Disappeared(self):
pass
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
signature="ao")
def CurrentActivityChanged(self, activities):
pass
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
signature="")
def IconChanged(self):
pass
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
signature="o")
def JoinedActivity(self, object_path):
pass
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
signature="o")
def LeftActivity(self, object_path):
pass
@dbus.service.signal(BUDDY_DBUS_INTERFACE,
signature="as")
def PropertyChanged(self, prop_list):
pass
@dbus.service.method(BUDDY_DBUS_INTERFACE,
in_signature="", out_signature="ay")
def getIcon(self):
icon = self._parent.get_icon()
if not icon:
return ""
return icon
@dbus.service.method(BUDDY_DBUS_INTERFACE,
in_signature="so", out_signature="o")
def getServiceOfType(self, stype, activity_op):
activity = None
# "/" is the placeholder for None
if activity_op != "/":
for act in self._parent.get_joined_activities():
if act.object_path() == activity_op:
activity = act
if not activity:
raise NotFoundError("Not found")
service = self._parent.get_service_of_type(stype, activity)
if not service:
raise NotFoundError("Not found")
return service.object_path()
@dbus.service.method(BUDDY_DBUS_INTERFACE,
in_signature="", out_signature="ao")
def getJoinedActivities(self):
acts = []
for act in self._parent.get_joined_activities():
acts.append(act.object_path())
return acts
@dbus.service.method(BUDDY_DBUS_INTERFACE,
in_signature="", out_signature="a{sv}")
def getProperties(self):
props = {}
props['name'] = self._parent.get_name()
addr = self._parent.get_address()
if addr:
props['ip4_address'] = addr
props['owner'] = self._parent.is_owner()
color = self._parent.get_color()
if color:
props[_BUDDY_KEY_COLOR] = color
return props
@dbus.service.method(BUDDY_DBUS_INTERFACE,
in_signature="", out_signature="o")
def getCurrentActivity(self):
activity = self._parent.get_current_activity()
if not activity:
raise NotFoundError()
return activity.object_path()
class Buddy(object):
"""Represents another person on the network and keeps track of the
activities and resources they make available for sharing."""
def __init__(self, bus_name, object_id, service, icon_cache):
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int):
raise ValueError("object id must be a valid number")
# Normal Buddy objects must be created with a valid service,
# owner objects do not
if not isinstance(self, Owner):
if not isinstance(service, Service.Service):
raise ValueError("service must be a valid service object")
self._services = {}
self._activities = {}
self._icon_cache = icon_cache
self._nick_name = None
self._address = None
if service is not None:
self._nick_name = service.get_name()
self._address = service.get_source_address()
self._color = None
self._current_activity = None
self._valid = False
self._icon = None
self._icon_tries = 0
self._object_id = object_id
self._object_path = BUDDY_DBUS_OBJECT_PATH + str(self._object_id)
self._dbus_helper = BuddyDBusHelper(self, bus_name, self._object_path)
self._buddy_presence_service = None
if service is not None:
self.add_service(service)
def object_path(self):
return dbus.ObjectPath(self._object_path)
def _request_buddy_icon_cb(self, result_status, response, user_data):
"""Callback when icon request has completed."""
from sugar.p2p import network
icon = response
service = user_data
if result_status == network.RESULT_SUCCESS:
if icon and len(icon):
icon = base64.b64decode(icon)
self._set_icon(icon)
self._icon_cache.add_icon(icon)
if (result_status == network.RESULT_FAILED or not icon) and self._icon_tries < 3:
self._icon_tries = self._icon_tries + 1
if self._icon_tries >= 3:
logging.debug("Failed to retrieve buddy icon for '%s'." % self._nick_name)
gobject.timeout_add(1000, self._get_buddy_icon, service, True)
return False
def _get_buddy_icon(self, service, retry=False):
"""Get the buddy's icon. Check the cache first, if its
not there get the icon from the buddy over the network."""
if retry != True:
# Only hit the cache once
icon_hash = service.get_one_property('icon-hash')
if icon_hash is not None:
icon = self._icon_cache.get_icon(icon_hash)
if icon:
logging.debug("%s: icon cache hit for %s." % (self._nick_name, icon_hash))
self._set_icon(icon)
return False
logging.debug("%s: icon cache miss, fetching icon from buddy..." % self._nick_name)
from sugar.p2p import Stream
buddy_stream = Stream.Stream.new_from_service(service, start_reader=False)
writer = buddy_stream.new_writer(service)
success = writer.custom_request("get_buddy_icon", self._request_buddy_icon_cb, service)
if not success:
del writer, buddy_stream
gobject.timeout_add(1000, self._get_buddy_icon, service, True)
return False
def _get_service_key(self, service):
return (service.get_type(), service.get_activity_id())
def add_service(self, service):
"""Adds a new service to this buddy's service list, returning
True if the service was successfully added, and False if it was not."""
if service.get_name() != self._nick_name:
logging.error("Service and buddy nick names doesn't match: " \
"%s %s" % (service.get_name(), self._nick_name))
return False
source_addr = service.get_source_address()
if source_addr != self._address:
logging.error("Service source and buddy address doesn't " \
"match: %s %s" % (source_addr, self._address))
return False
return self._internal_add_service(service)
def _internal_add_service(self, service):
service_key = self._get_service_key(service)
if service_key in self._services.keys():
logging.error("Service already known: %s %s" % (service_key[0],
service_key[1]))
return False
if service.get_type() == PRESENCE_SERVICE_TYPE and self._buddy_presence_service:
# already have a presence service for this buddy
logging.debug("!!! Tried to add a buddy presence service when " \
"one already existed.")
return False
logging.debug("Buddy %s added service type %s id %s" % (self._nick_name,
service.get_type(), service.get_activity_id()))
self._services[service_key] = service
service.set_owner(self)
if service.get_type() == PRESENCE_SERVICE_TYPE:
self._buddy_presence_service = service
# A buddy isn't valid until its official presence
# service has been found and resolved
self._valid = True
### TRIAL1: disable
#self._get_buddy_icon(service)
color = service.get_one_property(_BUDDY_KEY_COLOR)
if xocolor.is_valid(color):
self._color = color
self._current_activity = service.get_one_property(_BUDDY_KEY_CURACT)
# Monitor further buddy property changes, like current activity
# and color
service.connect('property-changed',
self.__buddy_presence_service_property_changed_cb)
if self._valid:
self._dbus_helper.ServiceAppeared(service.object_path())
return True
def __buddy_presence_service_property_changed_cb(self, service, keys):
if _BUDDY_KEY_COLOR in keys:
new_color = service.get_one_property(_BUDDY_KEY_COLOR)
if new_color and self._color != new_color and xocolor.is_valid(new_color):
self._color = new_color
self._dbus_helper.PropertyChanged([_BUDDY_KEY_COLOR])
if _BUDDY_KEY_CURACT in keys:
# Three cases here:
# 1) Buddy didn't publish a 'curact' key at all; we do nothing
# 2) Buddy published a blank/zero-length 'curact' key; we send
# a current-activity-changed signal for no activity
# 3) Buddy published a non-zero-length 'curact' key; we send
# a current-activity-changed signal if we know about the
# activity already, if not we postpone until the activity
# is found on the network and added to the buddy
new_curact = service.get_one_property(_BUDDY_KEY_CURACT)
if new_curact and self._current_activity != new_curact:
if not len(new_curact):
new_curact = None
self._current_activity = new_curact
if self._activities.has_key(self._current_activity):
# Case (3) above, valid activity id
activity = self._activities[self._current_activity]
if activity.is_valid():
self._dbus_helper.CurrentActivityChanged([activity.object_path()])
elif not self._current_activity:
# Case (2) above, no current activity
self._dbus_helper.CurrentActivityChanged([])
def __find_service_by_activity_id(self, actid):
for serv in self._services.values():
if serv.get_activity_id() == actid:
return serv
return None
def add_activity(self, activity):
if activity in self._activities.values():
return
actid = activity.get_id()
if not self.__find_service_by_activity_id(actid):
raise RuntimeError("Tried to add activity for which we had no service")
self._activities[actid] = activity
if activity.is_valid():
self._dbus_helper.JoinedActivity(activity.object_path())
# If when we received a current activity update from the buddy,
# but didn't know about that activity yet, and now we do know about
# it, we need to send out the changed activity signal
if actid == self._current_activity:
self._dbus_helper.CurrentActivityChanged([activity.object_path()])
def remove_service(self, service):
"""Remove a service from a buddy; ie, the activity was closed
or the buddy went away."""
if service.get_source_address() != self._address:
return
if service.get_name() != self._nick_name:
return
if service.get_type() == PRESENCE_SERVICE_TYPE \
and self._buddy_presence_service \
and service != self._buddy_presence_service:
logging.debug("!!! Tried to remove a spurious buddy presence service.")
return
service_key = self._get_service_key(service)
if self._services.has_key(service_key):
if self._valid:
self._dbus_helper.ServiceDisappeared(service.object_path())
del self._services[service_key]
if service.get_type() == PRESENCE_SERVICE_TYPE:
self._valid = False
self._dbus_helper.Disappeared()
def remove_activity(self, activity):
actid = activity.get_id()
if not self._activities.has_key(actid):
return
del self._activities[actid]
if activity.is_valid():
self._dbus_helper.LeftActivity(activity.object_path())
# If we just removed the buddy's current activity,
# send out a signal
if actid == self._current_activity:
self._current_activity = None
self._dbus_helper.CurrentActivityChanged([])
def get_joined_activities(self):
acts = []
for act in self._activities.values():
if act.is_valid():
acts.append(act)
return acts
def get_service_of_type(self, stype, activity=None):
"""Return a service of a certain type, or None if the buddy
doesn't provide that service."""
if not stype:
raise RuntimeError("Need to specify a service type.")
if activity and not activity.is_valid():
raise RuntimeError("Activity is not yet valid.")
if activity:
key = (stype, activity.get_id())
else:
key = (stype, None)
if self._services.has_key(key):
return self._services[key]
return None
def is_valid(self):
"""Return whether the buddy is valid or not. A buddy is
not valid until its official presence service has been found
and successfully resolved."""
return self._valid
def get_icon(self):
"""Return the buddies icon, if any."""
return self._icon
def get_address(self):
return self._address
def get_name(self):
return self._nick_name
def get_color(self):
return self._color
def get_current_activity(self):
if not self._current_activity:
return None
if not self._activities.has_key(self._current_activity):
return None
return self._activities[self._current_activity]
def _set_icon(self, icon):
"""Can only set icon for other buddies. The Owner
takes care of setting it's own icon."""
if icon != self._icon:
self._icon = icon
self._dbus_helper.IconChanged()
def is_owner(self):
return False
class Owner(Buddy):
"""Class representing the owner of the machine. This is the client
portion of the Owner, paired with the server portion in Owner.py."""
def __init__(self, ps, bus_name, object_id, icon_cache):
Buddy.__init__(self, bus_name, object_id, None, icon_cache)
self._nick_name = profile.get_nick_name()
self._color = profile.get_color().to_string()
self._ps = ps
def add_service(self, service):
"""Adds a new service to this buddy's service list, returning
True if the service was successfully added, and False if it was not."""
if service.get_name() != self._nick_name:
logging.error("Service and buddy nick names doesn't match: " \
"%s %s" % (service.get_name(), self._nick_name))
return False
# The Owner initially doesn't have an address, so the first
# service added to the Owner determines the owner's address
source_addr = service.get_source_address()
if self._address is None and service.is_local():
self._address = source_addr
self._dbus_helper.PropertyChanged(['ip4_address'])
# The owner bypasses address checks and only cares if
# avahi says the service is a local service
if not service.is_local():
logging.error("Cannot add remote service to owner object.")
return False
logging.debug("Adding owner service %s.%s at %s:%d." % (service.get_name(),
service.get_type(), service.get_source_address(),
service.get_port()))
return self._internal_add_service(service)
def is_owner(self):
return True
#################################################################
# Tests
#################################################################
import unittest
import Service
__objid_seq = 0
def _next_objid():
global __objid_seq
__objid_seq = __objid_seq + 1
return __objid_seq
class BuddyTestCase(unittest.TestCase):
_DEF_NAME = u"Tommy"
_DEF_STYPE = unicode(PRESENCE_SERVICE_TYPE)
_DEF_DOMAIN = u"local"
_DEF_ADDRESS = u"1.1.1.1"
_DEF_PORT = 1234
def __init__(self, name):
self._bus = dbus.SessionBus()
self._bus_name = dbus.service.BusName('org.laptop.Presence', bus=self._bus)
unittest.TestCase.__init__(self, name)
def __del__(self):
del self._bus_name
del self._bus
def _test_init_fail(self, service, fail_msg):
"""Test something we expect to fail."""
try:
objid = _next_objid()
buddy = Buddy(self._bus_name, objid, service, owner=False)
except ValueError, exc:
pass
else:
self.fail("expected a ValueError for %s." % fail_msg)
def testService(self):
service = None
self._test_init_fail(service, "invalid service")
def testGoodInit(self):
objid = _next_objid()
service = Service.Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN,
self._DEF_ADDRESS, self._DEF_PORT)
objid = _next_objid()
buddy = Buddy(self._bus_name, objid, service)
assert buddy.get_name() == self._DEF_NAME, "buddy name wasn't correct after init."
assert buddy.get_address() == self._DEF_ADDRESS, "buddy address wasn't correct after init."
assert buddy.object_path() == BUDDY_DBUS_OBJECT_PATH + str(objid)
def addToSuite(suite):
suite.addTest(BuddyTestCase("testService"))
suite.addTest(BuddyTestCase("testGoodInit"))
addToSuite = staticmethod(addToSuite)
def main():
suite = unittest.TestSuite()
BuddyTestCase.addToSuite(suite)
runner = unittest.TextTestRunner()
runner.run(suite)
if __name__ == "__main__":
main()

View File

@ -1,77 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os, time, md5
from sugar import env
from sugar import util
class BuddyIconCache(object):
"""Caches icons on disk and finds them based on md5 hash."""
def __init__(self):
ppath = env.get_profile_path()
self._cachepath = os.path.join(ppath, "cache", "buddy-icons")
if not os.path.exists(self._cachepath):
os.makedirs(self._cachepath)
self._cache = {}
# Read all cached icons and their sums
for fname in os.listdir(self._cachepath):
m = md5.new()
data = self._get_icon_data(fname)
if len(data) == 0:
continue
m.update(data)
printable_hash = util.printable_hash(m.digest())
self._cache[printable_hash] = fname
del m
def _get_icon_data(self, fname):
fd = open(os.path.join(self._cachepath, fname), "r")
data = fd.read()
fd.close()
del fd
return data
def get_icon(self, printable_hash):
if not isinstance(printable_hash, unicode):
raise RuntimeError("printable_hash must be a unicode string.")
try:
fname = self._cache[printable_hash]
return self._get_icon_data(fname)
except KeyError:
pass
return None
def add_icon(self, icon_data):
if len(icon_data) == 0:
return
m = md5.new()
m.update(icon_data)
printable_hash = util.printable_hash(m.digest())
if self._cache.has_key(printable_hash):
del m
return
# Write the icon to disk and add an entry to our cache for it
m.update(time.asctime())
fname = util.printable_hash(m.digest())
fd = open(os.path.join(self._cachepath, fname), "w")
fd.write(icon_data)
fd.close()
self._cache[printable_hash] = fname
del m

View File

@ -1,21 +1,13 @@
servicedir = $(datadir)/dbus-1/services
service_in_files = org.laptop.Presence.service.in
service_DATA = $(service_in_files:.service.in=.service)
sugardir = $(pkgdatadir)/services/presence2
sugar_PYTHON = \
__init__.py \
activity.py \
buddy.py \
buddyiconcache.py \
linklocal_plugin.py \
presenceservice.py \
server_plugin.py
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
bin_SCRIPTS = sugar-presence-service2
sugardir = $(pkgdatadir)/services/presence
sugar_PYTHON = \
__init__.py \
Activity.py \
Buddy.py \
BuddyIconCache.py \
PresenceService.py \
Service.py
bin_SCRIPTS = sugar-presence-service
DISTCLEANFILES = $(service_DATA)
EXTRA_DIST = $(service_in_files) $(bin_SCRIPTS)
EXTRA_DIST = $(bin_SCRIPTS)

View File

@ -1,876 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import avahi, dbus, dbus.glib, gobject
import Buddy
import Service
import Activity
import random
import logging
from sugar import util
from sugar import env
from sugar import profile
import BuddyIconCache
_SA_UNRESOLVED = 0
_SA_RESOLVE_PENDING = 1
_SA_RESOLVED = 2
class ServiceAdv(object):
"""Wrapper class to track services from Avahi."""
def __init__(self, interface, protocol, name, stype, domain, local):
self._interface = interface
self._protocol = protocol
if not isinstance(name, unicode):
raise ValueError("service advertisement name must be unicode.")
self._name = name
if not isinstance(stype, unicode):
raise ValueError("service advertisement type must be unicode.")
self._stype = stype
if not isinstance(domain, unicode):
raise ValueError("service advertisement domain must be unicode.")
self._domain = domain
self._service = None
if not isinstance(local, bool):
raise ValueError("local must be a bool.")
self._local = local
self._state = _SA_UNRESOLVED
self._resolver = None
self._resolv_tries = 0
def __del__(self):
if self._resolver:
del self._resolver
def interface(self):
return self._interface
def protocol(self):
return self._protocol
def name(self):
return self._name
def stype(self):
return self._stype
def domain(self):
return self._domain
def is_local(self):
return self._local
def resolv_tries(self):
return self._resolv_tries
def inc_resolv_tries(self):
self._resolv_tries += 1
def service(self):
return self._service
def set_service(self, service):
if not isinstance(service, Service.Service):
raise ValueError("must be a valid service.")
if service != self._service:
self._service = service
def resolver(self):
return self._resolver
def set_resolver(self, resolver):
if resolver and not isinstance(resolver, dbus.Interface):
raise ValueError("'resolver' must be a valid dbus object")
if not resolver and self._resolver:
del self._resolver
self._resolver = resolver
def state(self):
return self._state
def set_state(self, state):
if state == _SA_RESOLVE_PENDING:
if self._state == _SA_RESOLVED:
raise ValueError("Can't reset to resolve pending from resolved.")
if state == _SA_UNRESOLVED:
self._resolv_tries = 0
self._state = state
class RegisteredServiceType(object):
def __init__(self, stype):
self._stype = stype
self._refcount = 1
def get_type(self):
return self._stype
def ref(self):
self._refcount += 1
def unref(self):
self._refcount -= 1
return self._refcount
def _txt_to_dict(txt):
"""Convert an avahi-returned TXT record formatted
as nested arrays of integers (from dbus) into a dict
of key/value string pairs."""
prop_dict = {}
props = avahi.txt_array_to_string_array(txt)
for item in props:
key = value = None
if '=' not in item:
# No = means a boolean value of true
key = item
value = True
else:
(key, value) = item.split('=', 1)
prop_dict[key] = value
return prop_dict
_PRESENCE_SERVICE = "org.laptop.Presence"
_PRESENCE_DBUS_INTERFACE = "org.laptop.Presence"
_PRESENCE_OBJECT_PATH = "/org/laptop/Presence"
class NotFoundError(Exception):
pass
class PresenceServiceDBusHelper(dbus.service.Object):
def __init__(self, parent, bus_name):
self._parent = parent
self._bus_name = bus_name
dbus.service.Object.__init__(self, bus_name, _PRESENCE_OBJECT_PATH)
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
signature="o")
def BuddyAppeared(self, object_path):
pass
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
signature="o")
def BuddyDisappeared(self, object_path):
pass
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
signature="o")
def ServiceAppeared(self, object_path):
pass
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
signature="o")
def ServiceDisappeared(self, object_path):
pass
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
signature="o")
def ActivityAppeared(self, object_path):
pass
@dbus.service.signal(_PRESENCE_DBUS_INTERFACE,
signature="o")
def ActivityDisappeared(self, object_path):
pass
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="", out_signature="ao")
def getServices(self):
ret = []
for serv in self._parent.get_services():
ret.append(serv.object_path())
return ret
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="ao")
def getServicesOfType(self, stype):
ret = []
for serv in self._parent.get_services_of_type(stype):
ret.append(serv.object_path())
return ret
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="", out_signature="ao")
def getActivities(self):
ret = []
for act in self._parent.get_activities():
ret.append(act.object_path())
return ret
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="o")
def getActivity(self, actid):
act = self._parent.get_activity(actid)
if not act:
raise NotFoundError("Not found")
return act.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="", out_signature="ao")
def getBuddies(self):
ret = []
for buddy in self._parent.get_buddies():
ret.append(buddy.object_path())
return ret
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="o")
def getBuddyByName(self, name):
buddy = self._parent.get_buddy_by_name(name)
if not buddy:
raise NotFoundError("Not found")
return buddy.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="o")
def getBuddyByAddress(self, addr):
buddy = self._parent.get_buddy_by_address(addr)
if not buddy:
raise NotFoundError("Not found")
return buddy.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="", out_signature="o")
def getOwner(self):
owner = self._parent.get_owner()
if not owner:
raise NotFoundError("Not found")
return owner.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="os", out_signature="o",
sender_keyword="sender")
def joinActivity(self, activity_op, stype, sender):
found_activity = None
acts = self._parent.get_activities()
for act in acts:
if act.object_path() == activity_op:
found_activity = act
break
if not found_activity:
raise NotFoundError("The activity %s was not found." % activity_op)
return self._parent.join_activity(found_activity, stype, sender)
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="ssa{ss}sis", out_signature="o",
sender_keyword="sender")
def shareActivity(self, activity_id, stype, properties, address, port,
domain, sender=None):
if not len(address):
address = None
service = self._parent.share_activity(activity_id, stype, properties, address,
port, domain, sender)
return service.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="ssa{ss}sis", out_signature="o",
sender_keyword="sender")
def registerService(self, name, stype, properties, address, port, domain,
sender=None):
if not len(address):
address = None
service = self._parent.register_service(name, stype, properties, address,
port, domain, sender)
return service.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="o", out_signature="",
sender_keyword="sender")
def unregisterService(self, service_op, sender):
found_serv = None
services = self._parent.get_services()
for serv in services:
if serv.object_path() == service_op:
found_serv = serv
break
if not found_serv:
raise NotFoundError("The service %s was not found." % service_op)
return self._parent.unregister_service(found_serv, sender)
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="")
def registerServiceType(self, stype):
self._parent.register_service_type(stype)
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="")
def unregisterServiceType(self, stype):
self._parent.unregister_service_type(stype)
@dbus.service.method(_PRESENCE_DBUS_INTERFACE)
def start(self):
self._parent.start()
class PresenceService(object):
def __init__(self):
# interface -> IP address: interfaces we've gotten events on so far
self._started = False
self._local_addrs = {}
self._next_object_id = 0
self._buddies = {} # nick -> Buddy
self._services = {} # (name, type) -> Service
self._activities = {} # activity id -> Activity
self._service_blacklist = {}
# Keep track of stuff we're already browsing
self._service_type_browsers = {}
self._service_browsers = {}
# Resolved service list
self._service_advs = []
# Service types we care about resolving
self._registered_service_types = []
# Set up the dbus service we provide
self._session_bus = dbus.SessionBus()
self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=self._session_bus)
self._dbus_helper = PresenceServiceDBusHelper(self, self._bus_name)
self._icon_cache = BuddyIconCache.BuddyIconCache()
# Our owner object
if profile.get_nick_name():
objid = self._get_next_object_id()
self._owner = Buddy.Owner(self, self._bus_name,
objid, self._icon_cache)
self._buddies[self._owner.get_name()] = self._owner
else:
self._owner = None
def start(self):
if self._started:
return
self._started = True
# Connect to Avahi for mDNS stuff
self._system_bus = dbus.SystemBus()
self._mdns_service = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME,
avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
# Always browse .local
self._new_domain_cb(avahi.IF_UNSPEC, avahi.PROTO_INET, "local")
# Connect to Avahi and start looking for stuff
domain_browser = self._mdns_service.DomainBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_INET,
"", avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))
db = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, domain_browser), avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
db.connect_to_signal('ItemNew', self._new_domain_cb_glue)
def _get_next_object_id(self):
"""Increment and return the object ID counter."""
self._next_object_id = self._next_object_id + 1
return self._next_object_id
def get_services(self):
return self._services.values()
def get_services_of_type(self, stype):
ret = []
for serv in self._services.values():
if serv.get_type() == stype:
ret.append(serv)
return ret
def get_activities(self):
# Only return valid activities
ret = []
for act in self._activities.values():
if act.is_valid():
ret.append(act)
return ret
def get_activity(self, actid):
if self._activities.has_key(actid):
act = self._activities[actid]
if act.is_valid():
return act
return None
def get_buddies(self):
buddies = []
for buddy in self._buddies.values():
if buddy.is_valid():
buddies.append(buddy)
return buddies
def get_buddy_by_name(self, name):
if self._buddies.has_key(name):
if self._buddies[name].is_valid():
return self._buddies[name]
return None
def get_buddy_by_address(self, address):
for buddy in self._buddies.values():
if buddy.get_address() == address and buddy.is_valid():
return buddy
return None
def get_owner(self):
return self._owner
def _find_service_adv(self, interface=None, protocol=None, name=None,
stype=None, domain=None, local=None):
"""Search a list of service advertisements for ones matching
certain criteria."""
adv_list = []
for adv in self._service_advs:
if interface and adv.interface() != interface:
continue
if protocol and adv.protocol() != protocol:
continue
if name and adv.name() != name:
continue
if stype and adv.stype() != stype:
continue
if domain and adv.domain() != domain:
continue
if local is not None and adv.is_local() != local:
continue
adv_list.append(adv)
return adv_list
def _find_registered_service_type(self, stype):
for item in self._registered_service_types:
if item.get_type() == stype:
return item
return None
def _handle_new_service_for_buddy(self, service, local):
"""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
buddy_was_valid = False
name = service.get_name()
buddy = None
try:
buddy = self._buddies[name]
buddy_was_valid = buddy.is_valid()
service_added = buddy.add_service(service)
if service_added:
self._dbus_helper.ServiceAppeared(service.object_path())
except KeyError:
source_addr = service.get_source_address()
objid = self._get_next_object_id()
buddy = Buddy.Buddy(self._bus_name, objid, service, self._icon_cache)
self._buddies[name] = buddy
self._dbus_helper.ServiceAppeared(service.object_path())
if not buddy_was_valid and buddy.is_valid():
self._dbus_helper.BuddyAppeared(buddy.object_path())
return buddy
def _handle_new_activity_service(self, service):
# If the serivce is an activity service, merge it into our activities list
actid = service.get_activity_id()
if not actid:
return
activity = None
was_valid = False
if not self._activities.has_key(actid):
objid = self._get_next_object_id()
activity = Activity.Activity(self._bus_name, objid, service)
self._activities[actid] = activity
else:
activity = self._activities[actid]
was_valid = activity.is_valid()
if activity:
activity.add_service(service)
# Add the activity to its buddy
# FIXME: use something other than name to attribute to buddy
try:
buddy = self._buddies[service.get_name()]
buddy.add_activity(activity)
except KeyError:
pass
if not was_valid and activity.is_valid():
self._dbus_helper.ActivityAppeared(activity.object_path())
def _handle_remove_activity_service(self, service):
actid = service.get_activity_id()
if not actid:
return
if not self._activities.has_key(actid):
return
activity = self._activities[actid]
activity.remove_service(service)
if len(activity.get_services()) == 0:
# Remove the activity from its buddy
# FIXME: use something other than name to attribute to buddy
try:
buddy = self._buddies[service.get_name()]
buddy.remove_activity(activity)
except KeyError:
pass
# Kill the activity
self._dbus_helper.ActivityDisappeared(activity.object_path())
del self._activities[actid]
def _service_resolved_cb(self, adv, interface, protocol, full_name,
stype, domain, host, aprotocol, address, port, txt, flags,
updated):
"""When the service discovery finally gets here, we've got enough
information about the service to assign it to a buddy."""
if updated == False:
logging.debug("Resolved service '%s' type '%s' domain '%s' to " \
" %s:%s" % (full_name, stype, domain, address, port))
if not adv in self._service_advs:
return False
if adv.state() != _SA_RESOLVED:
return False
# See if we know about this service already
service = None
key = (full_name, stype)
props = _txt_to_dict(txt)
if not self._services.has_key(key):
objid = self._get_next_object_id()
service = Service.Service(self._bus_name, objid, name=full_name,
stype=stype, domain=domain, address=address, port=port,
properties=props, source_address=address)
self._services[key] = service
else:
# Already tracking this service; either:
# a) we were the one that shared it in the first place,
# and therefore the source address would not have
# been set yet
# b) the service has been updated
service = self._services[key]
if not service.get_source_address():
service.set_source_address(address)
if not service.get_address():
service.set_address(address)
adv.set_service(service)
if service and updated:
service.set_properties(props, from_network=True)
return False
# Merge the service into our buddy and activity lists, if needed
buddy = self._handle_new_service_for_buddy(service, adv.is_local())
if buddy and service.get_activity_id():
self._handle_new_activity_service(service)
return False
def _service_resolved_cb_glue(self, adv, interface, protocol, name,
stype, domain, host, aprotocol, address, port, txt, flags):
# Avahi doesn't flag updates to existing services, so we have
# to determine that here
updated = False
if adv.state() == _SA_RESOLVED:
updated = True
adv.set_state(_SA_RESOLVED)
gobject.idle_add(self._service_resolved_cb, adv, interface,
protocol, name, stype, domain, host, aprotocol, address,
port, txt, flags, updated)
def _service_resolved_failure_cb(self, adv, err):
retried = False
adv.set_resolver(None)
if adv.stype() == Buddy.PRESENCE_SERVICE_TYPE:
# Retry the presence service type a few times
if adv.resolv_tries() < 4:
adv.set_state(_SA_RESOLVE_PENDING)
gobject.timeout_add(250, self._resolve_service, adv)
retried = True
logging.error("Retrying resolution of service %s.%s: %s" % (adv.name(),
adv.stype(), err))
else:
key = adv.name() + "." + adv.stype()
if not self._service_blacklist.has_key(key):
logging.error("Adding service %s to blacklist" % key)
self._service_blacklist[key] = 1
if not retried:
logging.error("Error resolving service %s.%s: %s" % (adv.name(),
adv.stype(), err))
adv.set_state(_SA_UNRESOLVED)
def _resolve_service(self, adv):
"""Resolve and lookup a ZeroConf service to obtain its address and TXT records."""
key = adv.name() + "." + adv.stype()
if self._service_blacklist.has_key(key):
return False
# Ask avahi to resolve this particular service
try:
path = self._mdns_service.ServiceResolverNew(dbus.Int32(adv.interface()),
dbus.Int32(adv.protocol()), adv.name(), adv.stype(), adv.domain(),
avahi.PROTO_INET, dbus.UInt32(0))
except dbus.DBusException, e:
if str(e).find("TooManyObjectsError") >= 0:
return False
raise e
resolver = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, path),
avahi.DBUS_INTERFACE_SERVICE_RESOLVER)
resolver.connect_to_signal('Found', lambda *args: self._service_resolved_cb_glue(adv, *args))
resolver.connect_to_signal('Failure', lambda *args: self._service_resolved_failure_cb(adv, *args))
adv.inc_resolv_tries()
adv.set_resolver(resolver)
return False
def _service_appeared_cb(self, interface, protocol, full_name, stype, domain, flags):
local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0
adv_list = self._find_service_adv(interface=interface, protocol=protocol,
name=full_name, stype=stype, domain=domain, local=local)
adv = None
if not adv_list:
adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name,
stype=stype, domain=domain, local=local)
self._service_advs.append(adv)
else:
adv = adv_list[0]
# Decompose service name if we can
(actid, buddy_name) = Service.decompose_service_name(full_name)
# If we care about the service right now, resolve it
resolve = False
item = self._find_registered_service_type(stype)
if actid is not None or item is not None:
resolve = True
if resolve and adv.state() == _SA_UNRESOLVED:
logging.debug("Found '%s' (%d) of type '%s' in domain" \
" '%s' on %i.%i; will resolve." % (full_name, flags, stype,
domain, interface, protocol))
adv.set_state(_SA_RESOLVE_PENDING)
gobject.idle_add(self._resolve_service, adv)
return False
def _service_appeared_cb_glue(self, interface, protocol, name, stype, domain, flags):
gobject.idle_add(self._service_appeared_cb, interface, protocol, name, stype, domain, flags)
def _service_disappeared_cb(self, interface, protocol, full_name, stype, domain, flags):
local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0
# If it's an unresolved service, remove it from our unresolved list
adv_list = self._find_service_adv(interface=interface, protocol=protocol,
name=full_name, stype=stype, domain=domain, local=local)
if not adv_list:
return False
# Get the service object; if none, we have nothing left to do
adv = adv_list[0]
service = adv.service()
self._service_advs.remove(adv)
del adv
if not service:
return False
logging.debug("Service %s.%s in domain %s on %i.%i disappeared." % (full_name,
stype, domain, interface, protocol))
self._dbus_helper.ServiceDisappeared(service.object_path())
self._handle_remove_activity_service(service)
# Decompose service name if we can
(actid, buddy_name) = Service.decompose_service_name(full_name)
# Remove the service from the buddy
try:
buddy = self._buddies[buddy_name]
except KeyError:
pass
else:
buddy.remove_service(service)
if not buddy.is_valid():
self._dbus_helper.BuddyDisappeared(buddy.object_path())
del self._buddies[buddy_name]
key = (service.get_full_name(), service.get_type())
del self._services[key]
return False
def _service_disappeared_cb_glue(self, interface, protocol, name, stype, domain, flags):
gobject.idle_add(self._service_disappeared_cb, interface, protocol, name, stype, domain, flags)
def _new_service_type_cb(self, interface, protocol, stype, domain, flags):
# Are we already browsing this domain for this type?
if self._service_browsers.has_key((interface, protocol, stype, domain)):
return
# Start browsing for all services of this type in this domain
try:
s_browser = self._mdns_service.ServiceBrowserNew(interface,
protocol, stype, domain, dbus.UInt32(0))
browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, s_browser),
avahi.DBUS_INTERFACE_SERVICE_BROWSER)
browser_obj.connect_to_signal('ItemNew', self._service_appeared_cb_glue)
browser_obj.connect_to_signal('ItemRemove', self._service_disappeared_cb_glue)
self._service_browsers[(interface, protocol, stype, domain)] = browser_obj
except dbus.DBusException:
logging.debug("Error browsing service type '%s'" % stype)
return False
def _new_service_type_cb_glue(self, interface, protocol, stype, domain, flags):
if len(stype) > 0:
gobject.idle_add(self._new_service_type_cb, interface, protocol,
stype, domain, flags)
def _new_domain_cb(self, interface, protocol, domain, flags=0):
"""Callback from Avahi when a new domain has been found. Start
browsing the new domain."""
# Only use .local for now...
if domain != "local":
return
# Are we already browsing this domain?
if self._service_type_browsers.has_key((interface, protocol, domain)):
return
# Start browsing this domain for the services its members offer
try:
st_browser = self._mdns_service.ServiceTypeBrowserNew(interface, protocol, domain, dbus.UInt32(0))
browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, st_browser),
avahi.DBUS_INTERFACE_SERVICE_TYPE_BROWSER)
except dbus.DBusException, exc:
str_exc = str(exc)
logging.error("got exception %s while attempting to browse domain %s on %i.%i" % (str_exc, domain, interface, protocol))
if str_exc.find("The name org.freedesktop.Avahi was not provided by any .service files") >= 0:
raise Exception("Avahi does not appear to be running. '%s'" % str_exc)
else:
raise exc
logging.debug("Browsing domain '%s' on %i.%i ..." % (domain, interface, protocol))
browser_obj.connect_to_signal('ItemNew', self._new_service_type_cb_glue)
self._service_type_browsers[(interface, protocol, domain)] = browser_obj
return False
def _new_domain_cb_glue(self, interface, protocol, domain, flags=0):
gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags)
def join_activity(self, activity, stype, sender):
services = activity.get_services_of_type(stype)
if not len(services):
raise NotFoundError("The service type %s was not present within " \
"the activity %s" % (stype, activity.object_path()))
act_service = services[0]
props = act_service.get_properties()
color = activity.get_color()
if color:
props['color'] = color
return self._share_activity(activity.get_id(), stype, properties,
act_service.get_address(), act_service.get_port(),
act_service.get_domain(), sender)
def share_activity(self, activity_id, stype, properties=None, address=None,
port=-1, domain=u"local", sender=None):
"""Convenience function to share an activity with other buddies."""
if not util.validate_activity_id(activity_id):
raise ValueError("invalid activity id")
owner_nick = self._owner.get_name()
real_name = Service.compose_service_name(owner_nick, activity_id)
if address and not isinstance(address, unicode):
raise ValueError("address must be a unicode string.")
if address == None and stype.endswith('_udp'):
# Use random currently unassigned multicast address
address = u"232.%d.%d.%d" % (random.randint(0, 254), random.randint(1, 254),
random.randint(1, 254))
properties['address'] = address
properties['port'] = port
if port and port != -1 and (not isinstance(port, int) or port <= 1024 or port >= 65535):
raise ValueError("port must be a number between 1024 and 65535")
color = self._owner.get_color()
if color:
properties['color'] = color
logging.debug('Share activity %s, type %s, address %s, port %d, " \
"properties %s' % (activity_id, stype, address, port,
properties))
return self.register_service(real_name, stype, properties, address,
port, domain, sender)
def register_service(self, name, stype, properties={}, address=None,
port=-1, domain=u"local", sender=None):
"""Register a new service, advertising it to other Buddies on the network."""
# Refuse to register if we can't get the dbus connection this request
# came from for some reason
if not sender:
raise RuntimeError("Service registration request must have a sender.")
(actid, person_name) = Service.decompose_service_name(name)
if self.get_owner() and person_name != self.get_owner().get_name():
raise RuntimeError("Tried to register a service that didn't have" \
" Owner nick as the service name!")
if not domain or not len(domain):
domain = u"local"
if not port or port == -1:
port = random.randint(4000, 65000)
objid = self._get_next_object_id()
service = Service.Service(self._bus_name, objid, name=name,
stype=stype, domain=domain, address=address, port=port,
properties=properties, source_address=None,
local_publisher=sender)
self._services[(name, stype)] = service
self.register_service_type(stype)
service.register(self._system_bus, self._mdns_service)
return service
def unregister_service(self, service, sender=None):
service.unregister(sender)
def register_service_type(self, stype):
"""Requests that the Presence service look for and recognize
a certain mDNS service types."""
if not isinstance(stype, unicode):
raise ValueError("service type must be a unicode string.")
# If we've already registered it as a service type, ref it and return
item = self._find_registered_service_type(stype)
if item is not None:
item.ref()
return
# Otherwise track this type now
obj = RegisteredServiceType(stype)
self._registered_service_types.append(obj)
# Find unresolved services that match the service type
# we're now interested in, and resolve them
resolv_list = []
# Find services of this type
resolv_list = self._find_service_adv(stype=stype)
# Request resolution for them if they aren't in-process already
for adv in resolv_list:
if adv.state() == _SA_UNRESOLVED:
adv.set_state(_SA_RESOLVE_PENDING)
gobject.idle_add(self._resolve_service, adv)
def unregister_service_type(self, stype):
"""Stop tracking a certain mDNS service."""
if not isinstance(stype, unicode):
raise ValueError("service type must be a unicode string.")
# if it was found, unref it and possibly remove it
item = self._find_registered_service_type(stype)
if not item:
return
if item.unref() <= 0:
self._registered_service_types.remove(item)
del item
def main():
loop = gobject.MainLoop()
ps = PresenceService()
try:
loop.run()
except KeyboardInterrupt:
print 'Ctrl+C pressed, exiting...'
if __name__ == "__main__":
main()

View File

@ -1,602 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import avahi
import sys, os
sys.path.insert(0, os.path.abspath("../../"))
from sugar import util
import dbus, dbus.service
import random
import logging
import gobject
def compose_service_name(name, activity_id):
if isinstance(name, str):
name = unicode(name)
if not name:
raise ValueError("name must be a valid string.")
if not activity_id:
return name
if not isinstance(name, unicode):
raise ValueError("name must be in unicode.")
composed = "%s [%s]" % (name, activity_id)
return composed
def decompose_service_name(name):
"""Break a service name into the name and activity ID, if we can."""
if not isinstance(name, unicode):
raise ValueError("name must be a valid unicode string.")
name_len = len(name)
if name_len < util.ACTIVITY_ID_LEN + 5:
return (None, name)
# check for activity id end marker
if name[name_len - 1] != "]":
return (None, name)
start = name_len - 1 - util.ACTIVITY_ID_LEN
end = name_len - 1
# check for activity id start marker
if name[start - 1] != "[" or name[start - 2] != " ":
return (None, name)
activity_id = name[start:end]
if not util.validate_activity_id(activity_id):
return (None, name)
return (activity_id, name[:start - 2])
def _one_dict_differs(dict1, dict2):
diff_keys = []
for key, value in dict1.items():
if not dict2.has_key(key) or dict2[key] != value:
diff_keys.append(key)
return diff_keys
def _dicts_differ(dict1, dict2):
diff_keys = []
diff1 = _one_dict_differs(dict1, dict2)
diff2 = _one_dict_differs(dict2, dict1)
for key in diff2:
if key not in diff1:
diff_keys.append(key)
diff_keys += diff1
return diff_keys
def _convert_properties_to_dbus_byte_array(props):
# Ensure properties are converted to ByteArray types
# because python sometimes can't figure that out
info = dbus.Array([], signature="aay")
for k, v in props.items():
info.append(dbus.types.ByteArray("%s=%s" % (k, v)))
return info
_ACTIVITY_ID_TAG = "ActivityID"
SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service"
SERVICE_DBUS_OBJECT_PATH = "/org/laptop/Presence/Services/"
class ServiceDBusHelper(dbus.service.Object):
"""Handle dbus requests and signals for Service objects"""
def __init__(self, parent, bus_name, object_path):
self._parent = parent
self._bus_name = bus_name
self._object_path = object_path
dbus.service.Object.__init__(self, bus_name, self._object_path)
@dbus.service.signal(SERVICE_DBUS_INTERFACE,
signature="as")
def PublishedValueChanged(self, keylist):
pass
@dbus.service.method(SERVICE_DBUS_INTERFACE,
in_signature="", out_signature="a{sv}")
def getProperties(self):
"""Return service properties."""
pary = {}
pary['name'] = self._parent.get_name()
pary['type'] = self._parent.get_type()
pary['domain'] = self._parent.get_domain()
actid = self._parent.get_activity_id()
if actid:
pary['activityId'] = actid
port = self._parent.get_port()
if port:
pary['port'] = self._parent.get_port()
addr = self._parent.get_address()
if addr:
pary['address'] = addr
source_addr = self._parent.get_source_address()
if source_addr:
pary['sourceAddress'] = source_addr
return pary
@dbus.service.method(SERVICE_DBUS_INTERFACE,
in_signature="s")
def getPublishedValue(self, key):
"""Return the value belonging to the requested key from the
service's TXT records."""
val = self._parent.get_one_property(key)
if not val:
raise KeyError("Value was not found.")
return val
@dbus.service.method(SERVICE_DBUS_INTERFACE,
in_signature="", out_signature="a{sv}")
def getPublishedValues(self):
pary = {}
props = self._parent.get_properties()
for key, value in props.items():
pary[key] = str(value)
return dbus.Dictionary(pary)
@dbus.service.method(SERVICE_DBUS_INTERFACE,
sender_keyword="sender")
def setPublishedValue(self, key, value, sender):
self._parent.set_property(key, value, sender)
@dbus.service.method(SERVICE_DBUS_INTERFACE,
in_signature="a{sv}", sender_keyword="sender")
def setPublishedValues(self, values, sender):
if not self._parent.is_local():
raise ValueError("Service was not not registered by requesting process!")
self._parent.set_properties(values, sender)
class Service(gobject.GObject):
"""Encapsulates information about a specific ZeroConf/mDNS
service as advertised on the network."""
__gsignals__ = {
'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self, bus_name, object_id, name, stype, domain=u"local",
address=None, port=-1, properties=None, source_address=None,
local_publisher=None):
gobject.GObject.__init__(self)
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int):
raise ValueError("object id must be a valid number")
# Validate immutable options
if name and not isinstance(name, unicode):
raise ValueError("name must be unicode.")
if not name or not len(name):
raise ValueError("must specify a valid service name.")
if stype and not isinstance(stype, unicode):
raise ValueError("service type must be in unicode.")
if not stype or not len(stype):
raise ValueError("must specify a valid service type.")
if not stype.endswith("._tcp") and not stype.endswith("._udp"):
raise ValueError("must specify a TCP or UDP service type.")
if not isinstance(domain, unicode):
raise ValueError("domain must be in unicode.")
if domain and domain != "local":
raise ValueError("must use the 'local' domain (for now).")
# ID of the D-Bus connection that published this service, if any.
# We only let the local publisher modify the service.
self._local_publisher = local_publisher
self._avahi_entry_group = None
(actid, real_name) = decompose_service_name(name)
self._name = real_name
self._full_name = name
self._stype = stype
self._domain = domain
self._port = -1
self.set_port(port)
self._properties = {}
self._dbus_helper = None
self._internal_set_properties(properties)
# Source address is the unicast source IP
self._source_address = None
if source_address is not None:
self.set_source_address(source_address)
# Address is the published address, could be multicast or unicast
self._address = None
if self._properties.has_key('address'):
self.set_address(self._properties['address'])
elif address is not None:
self.set_address(address)
self._properties['address'] = address
# Ensure that an ActivityID tag, if given, matches
# what we expect from the service type
if self._properties.has_key(_ACTIVITY_ID_TAG):
prop_actid = self._properties[_ACTIVITY_ID_TAG]
if (prop_actid and not actid) or (prop_actid != actid):
raise ValueError("ActivityID property specified, but the " \
"service names's activity ID didn't match it: %s," \
" %s" % (prop_actid, actid))
self._activity_id = actid
if actid and not self._properties.has_key(_ACTIVITY_ID_TAG):
self._properties[_ACTIVITY_ID_TAG] = actid
self._owner = None
# register ourselves with dbus
self._object_id = object_id
self._object_path = SERVICE_DBUS_OBJECT_PATH + str(self._object_id)
self._dbus_helper = ServiceDBusHelper(self, bus_name, self._object_path)
def object_path(self):
return dbus.ObjectPath(self._object_path)
def get_owner(self):
return self._owner
def set_owner(self, owner):
if self._owner is not None:
raise RuntimeError("Can only set a service's owner once")
self._owner = owner
def is_local(self):
if self._local_publisher is not None:
return True
return False
def get_name(self):
"""Return the service's name, usually that of the
buddy who provides it."""
return self._name
def get_full_name(self):
return self._full_name
def get_one_property(self, key):
"""Return one property of the service, or None
if the property was not found. Cannot distinguish
between lack of a property, and a property value that
actually is None."""
if key in self._properties.keys():
return self._properties[key]
return None
def get_properties(self):
"""Return a python dictionary of all the service's
properties."""
return self._properties
def __emit_properties_changed_signal(self, keys):
if self._dbus_helper:
self._dbus_helper.PublishedValueChanged(keys)
self.emit('property-changed', keys)
def set_property(self, key, value, sender=None):
"""Set one service property"""
if not self._local_publisher:
raise ValueError("Service was not not registered by requesting process!")
if sender is not None and self._local_publisher != sender:
raise ValueError("Service was not not registered by requesting process!")
if not isinstance(key, unicode):
raise ValueError("Key must be a unicode string.")
if not isinstance(value, unicode) and not isinstance(value, bool):
raise ValueError("Key must be a unicode string or a boolean.")
# Ignore setting the key to it's current value
if self._properties.has_key(key):
if self._properties[key] == value:
return
# Blank value means remove key
remove = False
if isinstance(value, unicode) and len(value) == 0:
remove = True
if isinstance(value, bool) and value == False:
remove = True
if remove:
# If the key wasn't present, return without error
if self._properties.has_key(key):
del self._properties[key]
else:
# Otherwise set it
if isinstance(value, bool):
value = ""
self._properties[key] = value
# if the service is locally published already, update the TXT records
if self._local_publisher and self._avahi_entry_group:
self.__internal_update_avahi_properties()
self.__emit_properties_changed_signal([key])
def set_properties(self, properties, sender=None, from_network=False):
"""Set all service properties in one call"""
if sender is not None and self._local_publisher != sender:
raise ValueError("Service was not not registered by requesting process!")
self._internal_set_properties(properties, from_network)
def _internal_set_properties(self, properties, from_network=False):
"""Set the service's properties from either an Avahi
TXT record (a list of lists of integers), or a
python dictionary."""
if not isinstance (properties, dict):
raise ValueError("Properties must be a dictionary.")
# Make sure the properties are actually different
diff_keys = _dicts_differ(self._properties, properties)
if len(diff_keys) == 0:
return
self._properties = {}
# Set key/value pairs on internal property list
for key, value in properties.items():
if len(key) == 0:
continue
tmp_key = key
tmp_val = value
if not isinstance(tmp_key, unicode):
tmp_key = unicode(tmp_key)
if not isinstance(tmp_val, unicode):
tmp_val = unicode(tmp_val)
self._properties[tmp_key] = tmp_val
# if the service is locally published already, update the TXT records
if self._local_publisher and self._avahi_entry_group and not from_network:
self.__internal_update_avahi_properties()
self.__emit_properties_changed_signal(diff_keys)
def __internal_update_avahi_properties(self):
info = _convert_properties_to_dbus_byte_array(self._properties)
self._avahi_entry_group.UpdateServiceTxt(avahi.IF_UNSPEC,
avahi.PROTO_UNSPEC, 0,
dbus.String(self._full_name), dbus.String(self._stype),
dbus.String(self._domain), info)
def get_type(self):
"""Return the service's service type."""
return self._stype
def get_activity_id(self):
"""Return the activity ID this service is associated with, if any."""
return self._activity_id
def get_port(self):
return self._port
def set_port(self, port):
if not isinstance(port, int) or (port <= 1024 and port > 65536):
raise ValueError("must specify a valid port number between 1024 and 65536.")
self._port = port
def get_source_address(self):
return self._source_address
def set_source_address(self, address):
if not address or not isinstance(address, unicode):
raise ValueError("address must be unicode")
self._source_address = address
def get_address(self):
return self._address
def set_address(self, address):
if not address or not isinstance(address, unicode):
raise ValueError("address must be a unicode string")
self._address = address
self._properties['address'] = address
def get_domain(self):
"""Return the ZeroConf/mDNS domain the service was found in."""
return self._domain
def register(self, system_bus, avahi_service):
if self._avahi_entry_group is not None:
raise RuntimeError("Service already registered!")
obj = system_bus.get_object(avahi.DBUS_NAME, avahi_service.EntryGroupNew())
self._avahi_entry_group = dbus.Interface(obj, avahi.DBUS_INTERFACE_ENTRY_GROUP)
info = _convert_properties_to_dbus_byte_array(self._properties)
logging.debug("Will register service with name='%s', stype='%s'," \
" domain='%s', address='%s', port=%d, info='%s'" % (self._full_name,
self._stype, self._domain, self._address, self._port, info))
self._avahi_entry_group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0,
dbus.String(self._full_name), dbus.String(self._stype),
dbus.String(self._domain), dbus.String(""), # let Avahi figure the 'host' out
dbus.UInt16(self._port), info)
self._avahi_entry_group.connect_to_signal('StateChanged', self.__entry_group_changed_cb)
self._avahi_entry_group.Commit()
def __entry_group_changed_cb(self, state, error):
pass
# logging.debug("** %s.%s Entry group changed: state %s, error %s" % (self._full_name, self._stype, state, error))
def unregister(self, sender):
# Refuse to unregister if we can't get the dbus connection this request
# came from for some reason
if not sender:
raise RuntimeError("Service registration request must have a sender.")
if not self._local_publisher:
raise ValueError("Service was not a local service provided by this laptop!")
if sender is not None and self._local_publisher != sender:
raise ValueError("Service was not registered by requesting process!")
if not self._avahi_entry_group:
raise ValueError("Service was not registered by requesting process!")
self._avahi_entry_group.Free()
del self._avahi_entry_group
self._avahi_entry_group = None
#################################################################
# Tests
#################################################################
import unittest
__objid_seq = 0
def _next_objid():
global __objid_seq
__objid_seq = __objid_seq + 1
return __objid_seq
class ServiceTestCase(unittest.TestCase):
_DEF_NAME = u"foobar"
_DEF_STYPE = u"_foo._bar._tcp"
_DEF_DOMAIN = u"local"
_DEF_ADDRESS = u"1.1.1.1"
_DEF_PORT = 1234
_DEF_PROPS = {'foobar': 'baz'}
_STR_TEST_ARGS = [None, 0, [], {}]
def __init__(self, name):
self._bus = dbus.SessionBus()
self._bus_name = dbus.service.BusName('org.laptop.Presence', bus=self._bus)
unittest.TestCase.__init__(self, name)
def __del__(self):
del self._bus_name
del self._bus
def _test_init_fail(self, name, stype, domain, address, port, properties, fail_msg):
"""Test something we expect to fail."""
try:
objid = _next_objid()
service = Service(self._bus_name, objid, name, stype, domain, address,
port, properties)
except ValueError, exc:
pass
else:
self.fail("expected a ValueError for %s." % fail_msg)
def testName(self):
for item in self._STR_TEST_ARGS:
self._test_init_fail(item, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
self._DEF_PORT, self._DEF_PROPS, "invalid name")
def testType(self):
for item in self._STR_TEST_ARGS:
self._test_init_fail(self._DEF_NAME, item, self._DEF_DOMAIN, self._DEF_ADDRESS,
self._DEF_PORT, self._DEF_PROPS, "invalid service type")
self._test_init_fail(self._DEF_NAME, u"_bork._foobar", self._DEF_DOMAIN, self._DEF_ADDRESS,
self._DEF_PORT, self._DEF_PROPS, "invalid service type")
def testDomain(self):
for item in self._STR_TEST_ARGS:
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, item, self._DEF_ADDRESS,
self._DEF_PORT, self._DEF_PROPS, "invalid domain")
# Only accept local for now
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, u"foobar", self._DEF_ADDRESS,
self._DEF_PORT, self._DEF_PROPS, "invalid domain")
# Make sure "" works
objid = _next_objid()
service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, u"",
self._DEF_ADDRESS, self._DEF_PORT, self._DEF_PROPS)
assert service, "Empty domain was not accepted!"
def testAddress(self):
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, [],
self._DEF_PORT, self._DEF_PROPS, "invalid address")
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, {},
self._DEF_PORT, self._DEF_PROPS, "invalid address")
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, 1234,
self._DEF_PORT, self._DEF_PROPS, "invalid address")
def testPort(self):
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
[], self._DEF_PROPS, "invalid port")
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
{}, self._DEF_PROPS, "invalid port")
self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
"adf", self._DEF_PROPS, "invalid port")
def testGoodInit(self):
objid = _next_objid()
service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN,
self._DEF_ADDRESS, self._DEF_PORT, self._DEF_PROPS)
assert service.get_name() == self._DEF_NAME, "service name wasn't correct after init."
assert service.get_type() == self._DEF_STYPE, "service type wasn't correct after init."
assert service.get_domain() == "local", "service domain wasn't correct after init."
assert service.get_address() == self._DEF_ADDRESS, "service address wasn't correct after init."
assert service.get_port() == self._DEF_PORT, "service port wasn't correct after init."
assert service.object_path() == SERVICE_DBUS_OBJECT_PATH + str(objid)
value = service.get_one_property('foobar')
assert value and value == 'baz', "service property wasn't correct after init."
def testAvahiProperties(self):
props = [[111, 114, 103, 46, 102, 114, 101, 101, 100, 101, 115, 107, 116, 111, 112, 46, 65, 118, 97, 104, 105, 46, 99, 111, 111, 107, 105, 101, 61, 50, 54, 48, 49, 53, 52, 51, 57, 53, 50]]
key = "org.freedesktop.Avahi.cookie"
expected_value = "2601543952"
objid = _next_objid()
service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN,
self._DEF_ADDRESS, self._DEF_PORT, props)
value = service.get_one_property(key)
assert value and value == expected_value, "service properties weren't correct after init."
value = service.get_one_property('bork')
assert not value, "service properties weren't correct after init."
def testBoolProperty(self):
props = [[111, 114, 103, 46, 102, 114, 101, 101, 100, 101, 115, 107, 116, 111, 112, 46, 65, 118, 97, 104, 105, 46, 99, 111, 111, 107, 105, 101]]
key = "org.freedesktop.Avahi.cookie"
expected_value = True
objid = _next_objid()
service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS,
self._DEF_PORT, props)
value = service.get_one_property(key)
assert value is not None and value == expected_value, "service properties weren't correct after init."
def testActivityService(self):
# Valid group service type, non-multicast address
actid = "4569a71b80805aa96a847f7ac1c407327b3ec2b4"
name = compose_service_name("Tommy", actid)
# Valid activity service name, None address
objid = _next_objid()
service = Service(self._bus_name, objid, name, self._DEF_STYPE, self._DEF_DOMAIN, None,
self._DEF_PORT, self._DEF_PROPS)
assert service.get_address() == None, "address was not None as expected!"
assert service.get_activity_id() == actid, "activity id was different than expected!"
# Valid activity service name and multicast address, ensure it works
mc_addr = u"224.0.0.34"
objid = _next_objid()
service = Service(self._bus_name, objid, name, self._DEF_STYPE, self._DEF_DOMAIN, mc_addr,
self._DEF_PORT, self._DEF_PROPS)
assert service.get_address() == mc_addr, "address was not expected address!"
def addToSuite(suite):
suite.addTest(ServiceTestCase("testName"))
suite.addTest(ServiceTestCase("testType"))
suite.addTest(ServiceTestCase("testDomain"))
suite.addTest(ServiceTestCase("testAddress"))
suite.addTest(ServiceTestCase("testPort"))
suite.addTest(ServiceTestCase("testGoodInit"))
suite.addTest(ServiceTestCase("testAvahiProperties"))
suite.addTest(ServiceTestCase("testBoolProperty"))
suite.addTest(ServiceTestCase("testActivityService"))
addToSuite = staticmethod(addToSuite)
def main():
suite = unittest.TestSuite()
ServiceTestCase.addToSuite(suite)
runner = unittest.TextTestRunner()
runner.run(suite)
if __name__ == "__main__":
main()

View File

@ -24,12 +24,12 @@ import os
from sugar import logger
from sugar import env
sys.path.insert(0, env.get_service_path('presence'))
sys.path.insert(0, env.get_service_path('presence2'))
logger.start('presenceservice')
import PresenceService
import presenceservice
logging.info('Starting presence service')
PresenceService.main()
presenceservice.main()

View File

@ -1,13 +0,0 @@
sugardir = $(pkgdatadir)/services/presence2
sugar_PYTHON = \
__init__.py \
activity.py \
buddy.py \
buddyiconcache.py \
linklocal_plugin.py \
presenceservice.py \
server_plugin.py
bin_SCRIPTS = sugar-presence-service2
EXTRA_DIST = $(bin_SCRIPTS)

View File

@ -1,35 +0,0 @@
#!/usr/bin/env python
# vi: ts=4 ai noet
#
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import logging
import sys
import os
from sugar import logger
from sugar import env
sys.path.insert(0, env.get_service_path('presence2'))
logger.start('presenceservice')
import presenceservice
logging.info('Starting presence service')
presenceservice.main()

View File

@ -1,120 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import gobject
import dbus
def _one_dict_differs(dict1, dict2):
diff_keys = []
for key, value in dict1.items():
if not dict2.has_key(key) or dict2[key] != value:
diff_keys.append(key)
return diff_keys
def _dicts_differ(dict1, dict2):
diff_keys = []
diff1 = _one_dict_differs(dict1, dict2)
diff2 = _one_dict_differs(dict2, dict1)
for key in diff2:
if key not in diff1:
diff_keys.append(key)
diff_keys += diff1
return diff_keys
class Service(gobject.GObject):
_PRESENCE_SERVICE = "org.laptop.Presence"
_SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service"
__gsignals__ = {
'published-value-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
gobject.GObject.__init__(self)
self._object_path = object_path
self._ps_new_object = new_obj_cb
self._ps_del_object = del_obj_cb
sobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
self._service = dbus.Interface(sobj, self._SERVICE_DBUS_INTERFACE)
self._service.connect_to_signal('PropertyChanged', self.__property_changed_cb)
self._service.connect_to_signal('PublishedValueChanged',
self.__published_value_changed_cb)
self._props = self._service.getProperties()
self._pubvals = self._service.getPublishedValues()
def object_path(self):
return self._object_path
def __property_changed_cb(self, prop_list):
self._props = self._service.getProperties()
def get_published_value(self, key):
return self._pubvals[key]
def get_published_values(self):
self._pubvals = self._service.getPublishedValues()
return self._pubvals
def set_published_value(self, key, value):
if self._pubvals.has_key(key):
if self._pubvals[key] == value:
return
self._pubvals[key] = value
self._service.setPublishedValue(key, value)
def set_published_values(self, vals):
self._service.setPublishedValues(vals)
self._pubvals = vals
def __published_value_changed_cb(self, keys):
oldvals = self._pubvals
self.get_published_values()
diff_keys = _dicts_differ(oldvals, self._pubvals)
if len(diff_keys) > 0:
self.emit('published-value-changed', diff_keys)
def get_name(self):
return self._props['name']
def get_type(self):
return self._props['type']
def get_domain(self):
return self._props['domain']
def get_address(self):
if self._props.has_key('address'):
return self._props['address']
return None
def get_activity_id(self):
if self._props.has_key('activityId'):
return self._props['activityId']
return None
def get_port(self):
if self._props.has_key('port'):
return self._props['port']
return None
def get_source_address(self):
if self._props.has_key('sourceAddress'):
return self._props['sourceAddress']
return None

View File

@ -17,9 +17,7 @@
import dbus, dbus.glib, gobject
import Buddy, Service, Activity
_ENABLED = True
import buddy, service, activity
class ObjectCache(object):
def __init__(self):
@ -41,9 +39,9 @@ class ObjectCache(object):
del self._cache[object_path]
DBUS_SERVICE = "org.laptop.Presence"
DBUS_INTERFACE = "org.laptop.Presence"
DBUS_PATH = "/org/laptop/Presence"
DBUS_SERVICE = "org.laptop.Sugar.Presence"
DBUS_INTERFACE = "org.laptop.Sugar.Presence"
DBUS_PATH = "/org/laptop/Sugar/Presence"
class PresenceService(gobject.GObject):
@ -53,10 +51,11 @@ class PresenceService(gobject.GObject):
([gobject.TYPE_PYOBJECT])),
'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'service-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'service-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
'activity-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
gobject.TYPE_PYOBJECT])),
'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
@ -76,10 +75,10 @@ class PresenceService(gobject.GObject):
DBUS_PATH), DBUS_INTERFACE)
self._ps.connect_to_signal('BuddyAppeared', self._buddy_appeared_cb)
self._ps.connect_to_signal('BuddyDisappeared', self._buddy_disappeared_cb)
self._ps.connect_to_signal('ServiceAppeared', self._service_appeared_cb)
self._ps.connect_to_signal('ServiceDisappeared', self._service_disappeared_cb)
self._ps.connect_to_signal('ActivityAppeared', self._activity_appeared_cb)
self._ps.connect_to_signal('ActivityDisappeared', self._activity_disappeared_cb)
self._ps.connect_to_signal('ActivityInvitation', self._activity_invitation_cb)
self._ps.connect_to_signal('PrivateInvitation', self._private_invitation_cb)
def _new_object(self, object_path):
obj = self._objcache.get(object_path)
@ -225,77 +224,9 @@ class PresenceService(gobject.GObject):
def unregister_service_type(self, stype):
self._ps.unregisterServiceType(stype)
class _MockPresenceService(gobject.GObject):
__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])),
'service-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'service-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self):
gobject.GObject.__init__(self)
def get_services(self):
return []
def get_services_of_type(self, stype):
return []
def get_activities(self):
return []
def get_activity(self, activity_id):
return None
def get_buddies(self):
return []
def get_buddy_by_name(self, name):
return None
def get_buddy_by_address(self, addr):
return None
def get_owner(self):
return None
def share_activity(self, activity, stype, properties={}, address=None, port=-1, domain=u"local"):
return None
def register_service(self, name, stype, properties={}, address=None, port=-1, domain=u"local"):
return None
def unregister_service(self, service):
pass
def register_service_type(self, stype):
pass
def unregister_service_type(self, stype):
pass
_ps = None
def get_instance():
global _ps
if not _ps:
if _ENABLED:
_ps = PresenceService()
else:
_ps = _MockPresenceService()
_ps = PresenceService()
return _ps
def start():
if _ENABLED:
bus = dbus.SessionBus()
ps = dbus.Interface(bus.get_object(DBUS_SERVICE, DBUS_PATH), DBUS_INTERFACE)
ps.start()