Initial documentation pass for buddy objects

This commit is contained in:
Mike's Primary Account 2007-04-20 16:25:37 -04:00
parent 79d17c14f4
commit c0c64809a0

View File

@ -1,3 +1,4 @@
"""An "actor" on the network, whether remote or local"""
# Copyright (C) 2007, Red Hat, Inc. # Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd. # Copyright (C) 2007, Collabora Ltd.
# #
@ -29,6 +30,7 @@ _BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner" _OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner"
class NotFoundError(dbus.DBusException): class NotFoundError(dbus.DBusException):
"""Raised when a given actor is not found on the network"""
def __init__(self): def __init__(self):
dbus.DBusException.__init__(self) dbus.DBusException.__init__(self)
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound' self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
@ -38,8 +40,26 @@ class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGOb
class Buddy(DBusGObject): class Buddy(DBusGObject):
"""Represents another person on the network and keeps track of the """Person on the network (tracks properties and shared activites)
activities and resources they make available for sharing."""
The Buddy is a collection of metadata describing a particular
actor/person on the network. The Buddy object tracks a set of
activities which the actor has shared with the presence service.
Buddies have a "valid" property which is used to flag Buddies
which are no longer reachable. That is, a Buddy may represent
a no-longer reachable target on the network.
The Buddy emits GObject events that the PresenceService uses
to track changes in its status.
Attributes:
_activities -- dictionary mapping activity ID to
activity.Activity objects
handles -- dictionary mapping telepresence client to
"handle" (XXX what's that)
"""
__gsignals__ = { __gsignals__ = {
'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
@ -62,6 +82,15 @@ class Buddy(DBusGObject):
} }
def __init__(self, bus_name, object_id, **kwargs): def __init__(self, bus_name, object_id, **kwargs):
"""Initialize the Buddy object
bus_name -- DBUS object bus name (identifier)
object_id -- the activity's unique identifier
kwargs -- used to initialize the object's properties
constructs a DBUS "object path" from the _BUDDY_PATH
and object_id
"""
if not bus_name: if not bus_name:
raise ValueError("DBus bus name must be valid") raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int): if not object_id or not isinstance(object_id, int):
@ -89,6 +118,10 @@ class Buddy(DBusGObject):
gobject.GObject.__init__(self, **kwargs) gobject.GObject.__init__(self, **kwargs)
def do_get_property(self, pspec): def do_get_property(self, pspec):
"""Retrieve current value for the given property specifier
pspec -- property specifier with a "name" attribute
"""
if pspec.name == "key": if pspec.name == "key":
return self._key return self._key
elif pspec.name == "icon": elif pspec.name == "icon":
@ -109,6 +142,14 @@ class Buddy(DBusGObject):
return self._owner return self._owner
def do_set_property(self, pspec, value): def do_set_property(self, pspec, value):
"""Set given property
pspec -- property specifier with a "name" attribute
value -- value to set
emits 'icon-changed' signal on icon setting
calls _update_validity on all calls
"""
if pspec.name == "icon": if pspec.name == "icon":
if str(value) != self._icon: if str(value) != self._icon:
self._icon = str(value) self._icon = str(value)
@ -129,27 +170,42 @@ class Buddy(DBusGObject):
@dbus.service.signal(_BUDDY_INTERFACE, @dbus.service.signal(_BUDDY_INTERFACE,
signature="ay") signature="ay")
def IconChanged(self, icon_data): def IconChanged(self, icon_data):
pass """Generates DBUS signal with icon_data"""
@dbus.service.signal(_BUDDY_INTERFACE, @dbus.service.signal(_BUDDY_INTERFACE,
signature="o") signature="o")
def JoinedActivity(self, activity_path): def JoinedActivity(self, activity_path):
pass """Generates DBUS signal when buddy joins activity
activity_path -- DBUS path to the activity object
"""
@dbus.service.signal(_BUDDY_INTERFACE, @dbus.service.signal(_BUDDY_INTERFACE,
signature="o") signature="o")
def LeftActivity(self, activity_path): def LeftActivity(self, activity_path):
pass """Generates DBUS signal when buddy leaves activity
activity_path -- DBUS path to the activity object
"""
@dbus.service.signal(_BUDDY_INTERFACE, @dbus.service.signal(_BUDDY_INTERFACE,
signature="a{sv}") signature="a{sv}")
def PropertyChanged(self, updated): def PropertyChanged(self, updated):
pass """Generates DBUS signal when buddy's property changes
updated -- updated property-set (dictionary) with the
Buddy's property (changed) values. Note: not the
full set of properties, just the changes.
"""
# dbus methods # dbus methods
@dbus.service.method(_BUDDY_INTERFACE, @dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="ay") in_signature="", out_signature="ay")
def GetIcon(self): def GetIcon(self):
"""Retrieve Buddy's icon data
returns empty string or dbus.ByteArray
"""
if not self.props.icon: if not self.props.icon:
return "" return ""
return dbus.ByteArray(self.props.icon) return dbus.ByteArray(self.props.icon)
@ -157,6 +213,11 @@ class Buddy(DBusGObject):
@dbus.service.method(_BUDDY_INTERFACE, @dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="ao") in_signature="", out_signature="ao")
def GetJoinedActivities(self): def GetJoinedActivities(self):
"""Retrieve set of Buddy's joined activities (paths)
returns list of dbus service paths for the Buddy's joined
activities
"""
acts = [] acts = []
for act in self.get_joined_activities(): for act in self.get_joined_activities():
acts.append(act.object_path()) acts.append(act.object_path())
@ -165,6 +226,18 @@ class Buddy(DBusGObject):
@dbus.service.method(_BUDDY_INTERFACE, @dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="a{sv}") in_signature="", out_signature="a{sv}")
def GetProperties(self): def GetProperties(self):
"""Retrieve set of Buddy's properties
returns dictionary of
nick : str(nickname)
owner : bool( whether this Buddy is an owner??? )
XXX what is the owner flag for?
key : str(public-key)
color: Buddy's icon colour
XXX what type?
current-activity: Buddy's current activity_id, or
"" if no current activity
"""
props = {} props = {}
props['nick'] = self.props.nick props['nick'] = self.props.nick
props['owner'] = self.props.owner props['owner'] = self.props.owner
@ -178,9 +251,16 @@ class Buddy(DBusGObject):
# methods # methods
def object_path(self): def object_path(self):
"""Retrieve our dbus.ObjectPath object"""
return dbus.ObjectPath(self._object_path) return dbus.ObjectPath(self._object_path)
def add_activity(self, activity): def add_activity(self, activity):
"""Add an activity to the Buddy's set of activities
activity -- activity.Activity instance
calls JoinedActivity
"""
actid = activity.props.id actid = activity.props.id
if self._activities.has_key(actid): if self._activities.has_key(actid):
return return
@ -189,6 +269,12 @@ class Buddy(DBusGObject):
self.JoinedActivity(activity.object_path()) self.JoinedActivity(activity.object_path())
def remove_activity(self, activity): def remove_activity(self, activity):
"""Remove the activity from the Buddy's set of activities
activity -- activity.Activity instance
calls LeftActivity
"""
actid = activity.props.id actid = activity.props.id
if not self._activities.has_key(actid): if not self._activities.has_key(actid):
return return
@ -197,6 +283,7 @@ class Buddy(DBusGObject):
self.LeftActivity(activity.object_path()) self.LeftActivity(activity.object_path())
def get_joined_activities(self): def get_joined_activities(self):
"""Retrieves list of still-valid activity objects"""
acts = [] acts = []
for act in self._activities.values(): for act in self._activities.values():
if act.props.valid: if act.props.valid:
@ -204,6 +291,14 @@ class Buddy(DBusGObject):
return acts return acts
def set_properties(self, properties): def set_properties(self, properties):
"""Set the given set of properties on the object
properties -- set of property values to set
if no change, no events generated
if change, generates property-changed and
calls _update_validity
"""
changed = False changed = False
if "nick" in properties.keys(): if "nick" in properties.keys():
nick = properties["nick"] nick = properties["nick"]
@ -234,6 +329,12 @@ class Buddy(DBusGObject):
self._update_validity() self._update_validity()
def _update_validity(self): def _update_validity(self):
"""Check whether we are now valid
validity is True if color, nick and key are non-null
emits validity-changed if we have changed validity
"""
try: try:
old_valid = self._valid old_valid = self._valid
if self._color and self._nick and self._key: if self._color and self._nick and self._key:
@ -247,6 +348,13 @@ class Buddy(DBusGObject):
self._valid = False self._valid = False
class GenericOwner(Buddy): class GenericOwner(Buddy):
"""Common functionality for Local User-like objects
The TestOwner wants to produce something *like* a
ShellOwner, but with randomised changes and the like.
This class provides the common features for a real
local owner and a testing one.
"""
__gtype_name__ = "GenericOwner" __gtype_name__ = "GenericOwner"
__gproperties__ = { __gproperties__ = {
@ -256,6 +364,15 @@ class GenericOwner(Buddy):
} }
def __init__(self, ps, bus_name, object_id, **kwargs): def __init__(self, ps, bus_name, object_id, **kwargs):
"""Initialize the GenericOwner instance
ps -- presenceservice.PresenceService object
bus_name -- DBUS object bus name (identifier)
object_id -- the activity's unique identifier
kwargs -- used to initialize the object's properties
calls Buddy.__init__
"""
self._ps = ps self._ps = ps
self._server = 'olpc.collabora.co.uk' self._server = 'olpc.collabora.co.uk'
self._key_hash = None self._key_hash = None
@ -274,21 +391,27 @@ class GenericOwner(Buddy):
self._owner = True self._owner = True
def get_registered(self): def get_registered(self):
"""Retrieve whether owner has registered with presence server"""
return self._registered return self._registered
def get_server(self): def get_server(self):
"""Retrieve presence server (XXX url??)"""
return self._server return self._server
def get_key_hash(self): def get_key_hash(self):
"""Retrieve the user's private-key hash"""
return self._key_hash return self._key_hash
def set_registered(self, registered): def set_registered(self, registered):
"""Customisation point: handle the registration of the owner"""
raise RuntimeError("Subclasses must implement") raise RuntimeError("Subclasses must implement")
class ShellOwner(GenericOwner): class ShellOwner(GenericOwner):
"""Class representing the owner of the machine. This is the client """Representation of the local-machine owner using Sugar's Shell
portion of the Owner, paired with the server portion in Owner.py."""
The ShellOwner uses the Sugar Shell's dbus services to
register for updates about the user's profile description.
"""
__gtype_name__ = "ShellOwner" __gtype_name__ = "ShellOwner"
_SHELL_SERVICE = "org.laptop.Shell" _SHELL_SERVICE = "org.laptop.Shell"
@ -296,6 +419,19 @@ class ShellOwner(GenericOwner):
_SHELL_PATH = "/org/laptop/Shell" _SHELL_PATH = "/org/laptop/Shell"
def __init__(self, ps, bus_name, object_id, test=False): def __init__(self, ps, bus_name, object_id, test=False):
"""Initialize the ShellOwner instance
ps -- presenceservice.PresenceService object
bus_name -- DBUS object bus name (identifier)
object_id -- the activity's unique identifier
test -- ignored
Retrieves initial property values from the profile
module. Loads the buddy icon from file as well.
XXX note: no error handling on that
calls GenericOwner.__init__
"""
server = profile.get_server() server = profile.get_server()
key_hash = profile.get_private_key_hash() key_hash = profile.get_private_key_hash()
registered = profile.get_server_registered() registered = profile.get_server_registered()
@ -325,10 +461,17 @@ class ShellOwner(GenericOwner):
pass pass
def set_registered(self, value): def set_registered(self, value):
"""Handle notification that we have been registered"""
if value: if value:
profile.set_server_registered() profile.set_server_registered()
def _name_owner_changed_handler(self, name, old, new): def _name_owner_changed_handler(self, name, old, new):
"""Handle DBUS notification of a new / renamed service
Watches for the _SHELL_SERVICE, i.e. the Sugar Shell,
and registers with it if we have not yet registered
with it (using _connect_to_shell).
"""
if name != self._SHELL_SERVICE: if name != self._SHELL_SERVICE:
return return
if (old and len(old)) and (not new and not len(new)): if (old and len(old)) and (not new and not len(new)):
@ -339,6 +482,11 @@ class ShellOwner(GenericOwner):
self._connect_to_shell() self._connect_to_shell()
def _connect_to_shell(self): def _connect_to_shell(self):
"""Connect to the Sugar Shell service to watch for events
Connects the various XChanged events on the Sugar Shell
service to our _x_changed_cb methods.
"""
obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH) obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH)
self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE) self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE)
self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb) self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb)
@ -348,17 +496,26 @@ class ShellOwner(GenericOwner):
self._cur_activity_changed_cb) self._cur_activity_changed_cb)
def _icon_changed_cb(self, icon): def _icon_changed_cb(self, icon):
"""Handle icon change, set property to generate event"""
self.props.icon = icon self.props.icon = icon
def _color_changed_cb(self, color): def _color_changed_cb(self, color):
"""Handle color change, set property to generate event"""
props = {'color': color} props = {'color': color}
self.set_properties(props) self.set_properties(props)
def _nick_changed_cb(self, nick): def _nick_changed_cb(self, nick):
"""Handle nickname change, set property to generate event"""
props = {'nick': nick} props = {'nick': nick}
self.set_properties(props) self.set_properties(props)
def _cur_activity_changed_cb(self, activity_id): def _cur_activity_changed_cb(self, activity_id):
"""Handle current-activity change, set property to generate event
Filters out local activities (those not in self.activites)
because the network users can't join those activities, so
the activity_id shared will be None in those cases...
"""
if not self._activities.has_key(activity_id): if not self._activities.has_key(activity_id):
# This activity is local-only # This activity is local-only
activity_id = None activity_id = None
@ -495,6 +652,7 @@ class TestOwner(GenericOwner):
def _hash_private_key(self): def _hash_private_key(self):
"""Unused method to has a private key, see profile"""
self.privkey_hash = None self.privkey_hash = None
key_path = os.path.join(env.get_profile_path(), 'owner.key') key_path = os.path.join(env.get_profile_path(), 'owner.key')
@ -545,6 +703,7 @@ def _extract_public_key(keyfile):
return key return key
def _extract_private_key(keyfile): def _extract_private_key(keyfile):
"""Get a private key from a private key file"""
# Extract the private key # Extract the private key
try: try:
f = open(keyfile, "r") f = open(keyfile, "r")
@ -568,6 +727,7 @@ def _extract_private_key(keyfile):
return key return key
def _get_new_keypair(num): def _get_new_keypair(num):
"""Retrieve a public/private key pair for testing"""
# Generate keypair # Generate keypair
privkeyfile = os.path.join("/tmp", "test%d.key" % num) privkeyfile = os.path.join("/tmp", "test%d.key" % num)
pubkeyfile = os.path.join("/tmp", 'test%d.key.pub' % num) pubkeyfile = os.path.join("/tmp", 'test%d.key.pub' % num)
@ -600,10 +760,12 @@ def _get_new_keypair(num):
return (pubkey, privkey) return (pubkey, privkey)
def _get_random_name(): def _get_random_name():
"""Produce random names for testing"""
names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"] names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"]
return names[random.randint(0, len(names) - 1)] return names[random.randint(0, len(names) - 1)]
def _get_random_image(): def _get_random_image():
"""Produce a random image for display"""
import cairo, math, random, gtk import cairo, math, random, gtk
def rand(): def rand():