From 2262b5fd4931fd20e5cd8e5732e68383b4b18786 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Thu, 8 Mar 2007 15:45:12 +0100 Subject: [PATCH 1/5] update nick when receive AliasChanged signal --- services/presence2/presenceservice.py | 2 +- services/presence2/server_plugin.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/services/presence2/presenceservice.py b/services/presence2/presenceservice.py index afccbbde..4ad780d9 100644 --- a/services/presence2/presenceservice.py +++ b/services/presence2/presenceservice.py @@ -134,7 +134,7 @@ class PresenceService(dbus.service.Object): buddy = self._handles_buddies[tp].get(handle) if buddy: buddy.set_properties(prop) - print "Buddy %s properties updated" % buddy.props.key + #print "Buddy %s properties updated" % buddy.props.key def _new_activity(self, activity_id, tp): objid = self._get_next_object_id() diff --git a/services/presence2/server_plugin.py b/services/presence2/server_plugin.py index 935da23a..bd15bcd5 100644 --- a/services/presence2/server_plugin.py +++ b/services/presence2/server_plugin.py @@ -218,9 +218,7 @@ class ServerPlugin(gobject.GObject): self._conn[CONN_INTERFACE_AVATARS].connect_to_signal('AvatarUpdated', self._avatar_updated_cb) - # FIXME: we need to use PEP to store the nick. We aren't notified when - # vcards are changed - #self._conn[CONN_INTERFACE_ALIASING].connect_to_signal('AliasesChanged', self._alias_changed_cb) + self._conn[CONN_INTERFACE_ALIASING].connect_to_signal('AliasesChanged', self._alias_changed_cb) try: self._set_self_buddy_info() @@ -409,9 +407,8 @@ class ServerPlugin(gobject.GObject): def _alias_changed_cb(self, aliases): for handle, alias in aliases: - nick = self._conn[CONN_INTERFACE_ALIASING].RequestAliases([handle])[0] - prop = {'nick': nick} - print "Buddy %s alias changed to %s" % (handle, nick) + prop = {'nick': alias} + #print "Buddy %s alias changed to %s" % (handle, alias) self._properties_changed_cb(handle, prop) def _properties_changed_cb(self, contact, properties): From afcfdaa2393e0cba1ac22da9fa693e7cd164aff1 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Thu, 8 Mar 2007 16:16:06 +0100 Subject: [PATCH 2/5] add GetType() method on Activity --- services/presence2/activity.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/presence2/activity.py b/services/presence2/activity.py index 3a4216c8..b79d2b06 100644 --- a/services/presence2/activity.py +++ b/services/presence2/activity.py @@ -29,6 +29,7 @@ class Activity(dbus.service.Object): self._valid = False self._name = None self._activity_id = activity_id + self._type None self._object_id = object_id self._object_path = "/org/laptop/Presence/Activities/%d" % self._object_id @@ -69,6 +70,11 @@ class Activity(dbus.service.Object): def GetColor(self): return self.get_color() + @dbus.service.method(_ACTIVITY_INTERFACE, + in_signature="", out_signature="s") + def GetType(self): + return self.get_type() + @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="") def Join(self): @@ -111,6 +117,9 @@ class Activity(dbus.service.Object): def get_name(self): return self._name + def get_type(self): + return self._type + def buddy_joined(self, buddy): if buddy not in self._buddies: self._buddies.append(buddy) From 6247c61ee7929732eae33e45da8586c191db7709 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Thu, 8 Mar 2007 16:43:04 +0100 Subject: [PATCH 3/5] add mising "=" --- services/presence2/activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/presence2/activity.py b/services/presence2/activity.py index b79d2b06..012366c9 100644 --- a/services/presence2/activity.py +++ b/services/presence2/activity.py @@ -29,7 +29,7 @@ class Activity(dbus.service.Object): self._valid = False self._name = None self._activity_id = activity_id - self._type None + self._type = None self._object_id = object_id self._object_path = "/org/laptop/Presence/Activities/%d" % self._object_id From d46382921adc42d5845cdefd255cb3ca5961eaca Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 8 Mar 2007 12:51:10 -0500 Subject: [PATCH 4/5] Convert Activity objects to gobjects and ensure validity before using --- services/presence2/activity.py | 153 +++++++++++++++++++------- services/presence2/buddy.py | 17 ++- services/presence2/presenceservice.py | 56 +++++++--- 3 files changed, 161 insertions(+), 65 deletions(-) diff --git a/services/presence2/activity.py b/services/presence2/activity.py index b79d2b06..656bb18f 100644 --- a/services/presence2/activity.py +++ b/services/presence2/activity.py @@ -15,33 +15,118 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import gobject import dbus, dbus.service +from sugar import util from telepathy.interfaces import (CHANNEL_INTERFACE) _ACTIVITY_PATH = "/org/laptop/Sugar/Presence/Activities/" _ACTIVITY_INTERFACE = "org.laptop.Sugar.Presence.Activity" -class Activity(dbus.service.Object): - def __init__(self, bus_name, object_id, activity_id, tp): - self._buddies = [] - self._color = None - self._valid = False - self._name = None - self._activity_id = activity_id - self._type None +class DBusGObjectMetaclass(gobject.GObjectMeta, dbus.service.InterfaceType): pass +class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass + + +class Activity(DBusGObject): + __gtype_name__ = "Activity" + + __gsignals__ = { + 'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_BOOLEAN])) + } + + __gproperties__ = { + 'id' : (str, None, None, None, + gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY), + 'name' : (str, None, None, None, gobject.PARAM_READWRITE), + 'color' : (str, None, None, None, gobject.PARAM_READWRITE), + 'type' : (str, None, None, None, gobject.PARAM_READWRITE), + 'valid' : (bool, None, None, False, gobject.PARAM_READABLE), + 'local' : (bool, None, None, False, + gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY), + 'joined' : (bool, None, None, False, gobject.PARAM_READABLE) + } + + def __init__(self, bus_name, object_id, tp, **kwargs): + 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") + if not tp: + raise ValueError("telepathy CM must be valid") self._object_id = object_id - self._object_path = "/org/laptop/Presence/Activities/%d" % self._object_id - + self._object_path = _ACTIVITY_PATH + str(self._object_id) + dbus.service.Object.__init__(self, bus_name, self._object_path) + + self._buddies = [] + self._joined = False + # the telepathy client self._tp = tp self._activity_text_channel = None - self._joined = False + self._valid = False + self._id = None + self._local = False + self._type = None - dbus.service.Object.__init__(self, bus_name, self._object_path) - + if not kwargs.get("id"): + raise ValueError("activity id is required") + if not util.validate_activity_id(kwargs['id']): + raise ValueError("Invalid activity id '%s'" % kwargs['id']) + + gobject.GObject.__init__(self, **kwargs) + if self.props.local and not self.props.valid: + raise RuntimeError("local activities require color, type, and name") + + def do_get_property(self, pspec): + if pspec.name == "id": + return self._id + elif pspec.name == "name": + return self._name + elif pspec.name == "color": + return self._color + elif pspec.name == "type": + return self._type + elif pspec.name == "valid": + return self._valid + elif pspec.name == "joined": + return self._joined + elif pspec.name == "local": + return self._local + + def do_set_property(self, pspec, value): + if pspec.name == "id": + self._id = value + elif pspec.name == "name": + self._name = value + elif pspec.name == "color": + self._color = value + elif pspec.name == "type": + if self._type: + raise RuntimeError("activity type is already set") + self._type = value + elif pspec.name == "joined": + self._joined = value + elif pspec.name == "local": + self._local = value + + self._update_validity() + + def _update_validity(self): + try: + old_valid = self._valid + if self._color and self._name and self._id and self._type: + self._valid = True + else: + self._valid = False + + if old_valid != self._valid: + self.emit("validity-changed", self._valid) + except AttributeError: + self._valid = False # dbus signals @dbus.service.signal(_ACTIVITY_INTERFACE, @@ -63,17 +148,17 @@ class Activity(dbus.service.Object): @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="s") def GetId(self): - return self.get_id() + return self.props.id @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="s") def GetColor(self): - return self.get_color() + return self.props.color @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="s") def GetType(self): - return self.get_type() + return self.props.type @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="") @@ -83,8 +168,10 @@ class Activity(dbus.service.Object): @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="ao") def GetJoinedBuddies(self): + ret = [] for buddy in self._buddies: - ret.append(buddy.object_path()) + if buddy.props.valid: + ret.append(buddy.object_path()) return ret @dbus.service.method(_ACTIVITY_INTERFACE, @@ -95,44 +182,34 @@ class Activity(dbus.service.Object): @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="s") def GetName(self): - return self.get_name() + return self.props.name # methods 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_joined_buddies(self): - return self._buddies - - def get_name(self): - return self._name - - def get_type(self): - return self._type + ret = [] + for buddy in self._buddies: + if buddy.props.valid: + ret.append(buddy) + return ret def buddy_joined(self, buddy): if buddy not in self._buddies: self._buddies.append(buddy) - self.BuddyJoined(buddy.object_path()) + if self.props.valid: + self.BuddyJoined(buddy.object_path()) def buddy_left(self, buddy): if buddy in self._buddies: self._buddies.remove(buddy) - self.BuddyLeft(buddy.object_path()) + if self.props.valid: + self.BuddyLeft(buddy.object_path()) def join(self): if not self._joined: - self._activity_text_channel = self._tp.join_activity(self._activity_id) + self._activity_text_channel = self._tp.join_activity(self.props.id) self._activity_text_channel[CHANNEL_INTERFACE].connect_to_signal('Closed', self._activity_text_channel_closed_cb) self._joined = True diff --git a/services/presence2/buddy.py b/services/presence2/buddy.py index 458d1247..756b3085 100644 --- a/services/presence2/buddy.py +++ b/services/presence2/buddy.py @@ -73,9 +73,10 @@ class Buddy(DBusGObject): self._key = None self._icon = '' + if not kwargs.get("key"): + raise ValueError("key required") + gobject.GObject.__init__(self, **kwargs) - if not self._key: - raise RuntimeError("public key required") def do_get_property(self, pspec): if pspec.name == "key": @@ -109,8 +110,6 @@ class Buddy(DBusGObject): elif pspec.name == "current-activity": self._current_activity = value elif pspec.name == "key": - if self._key: - raise RuntimeError("key already set") self._key = value self._update_validity() @@ -167,25 +166,25 @@ class Buddy(DBusGObject): return dbus.ObjectPath(self._object_path) def add_activity(self, activity): - actid = activity.get_id() + actid = activity.props.id if self._activities.has_key(actid): return self._activities[actid] = activity - if activity.is_valid(): + if activity.props.valid: self.JoinedActivity(activity.object_path()) def remove_activity(self, activity): - actid = activity.get_id() + actid = activity.props.id if not self._activities.has_key(actid): return del self._activities[actid] - if activity.is_valid(): + if activity.props.valid: self.LeftActivity(activity.object_path()) def get_joined_activities(self): acts = [] for act in self._activities.values(): - if act.is_valid(): + if act.props.valid: acts.append(act) return acts diff --git a/services/presence2/presenceservice.py b/services/presence2/presenceservice.py index 4ad780d9..2018ec1c 100644 --- a/services/presence2/presenceservice.py +++ b/services/presence2/presenceservice.py @@ -137,22 +137,35 @@ class PresenceService(dbus.service.Object): #print "Buddy %s properties updated" % buddy.props.key def _new_activity(self, activity_id, tp): - objid = self._get_next_object_id() - activity = Activity(self._bus_name, objid, activity_id, tp) - # FIXME : don't do that shit ! - activity._valid = True + try: + objid = self._get_next_object_id() + activity = Activity(self._bus_name, objid, tp, id=activity_id) + except Exception, e: + print "Invalid activity: %s" % e + return None + + activity.connect("validity-changed", self._activity_validity_changed_cb) + self._activities[activity_id] = activity - print "new activity", activity_id - self.ActivityAppeared(activity.object_path()) + # FIXME + # Use values from the network + import random + names = ["Tommy", "Susie", "Jill", "Bryan", "Nathan", "Sophia", "Haley", "Jimmy"] + name = names[random.randint(0, len(names) - 1)] + activity.props.name = "Chat with %s" % name + activity.props.type = "org.laptop.Sugar.Chat" + from sugar.graphics import xocolor + color = xocolor.XoColor().to_string() + activity.props.color = color return activity def _remove_activity(self, activity): - print "remove activity", activity.get_id() + print "remove activity", activity.props.id self.ActivityDisappeared(activity.object_path()) - del self._activities[activity.get_id()] + del self._activities[activity.props.id] def _contact_activities_changed(self, tp, contact_handle, activities): print "------------activities changed-------------" @@ -168,7 +181,7 @@ class PresenceService(dbus.service.Object): old_activities = set() for activity in buddy.get_joined_activities(): - old_activities.add(activity.get_id()) + old_activities.add(activity.props.id) new_activities = set(activities) @@ -177,11 +190,12 @@ class PresenceService(dbus.service.Object): print "buddy", contact_handle, "joined", act activity = self._activities.get(act) if not activity: - # new activity + # new activity, can fail activity = self._new_activity(act, tp) - activity.buddy_joined(buddy) - buddy.add_activity(activity) + if activity: + activity.buddy_joined(buddy) + buddy.add_activity(activity) activities_left = old_activities - new_activities for act in activities_left: @@ -278,18 +292,24 @@ class PresenceService(dbus.service.Object): def _share_activity(self, actid, atype, name, properties): objid = self._get_next_object_id() # FIXME check which tp client we should use to share the activity - activity = Activity(self._bus_name, objid, actid, self._server_plugin) - # FIXME : don't do that shit ! - activity._valid = True + color = self._owner.props.color + activity = Activity(self._bus_name, objid, self._server_plugin, + id=actid, type=atype, name=name, color=color, local=True) + activity.connect("validity-changed", self._activity_validity_changed_cb) self._activities[actid] = activity - # FIXME set the type, name, properties... - print "new activity", actid activity.join() - self.ActivityAppeared(activity.object_path()) return activity + def _activity_validity_changed_cb(self, activity, valid): + if valid: + self.ActivityAppeared(activity.object_path()) + print "New Activity: %s (%s)" % (activity.props.name, activity.props.id) + else: + self.ActivityDisappeared(activity.object_path()) + print "Activity disappeared: %s (%s)" % (activity.props.name, activity.props.id) + def main(): loop = gobject.MainLoop() From 8dc201bc5f166a84a50535252d35e8f9586813e4 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 8 Mar 2007 22:17:33 -0500 Subject: [PATCH 5/5] Expose owner details through the Shell's DBus service For security, we need the PresenceService to listen for changes to the owner's attributes, like changed color, nickname, icon, and current activity, rather than having D-Bus API in the PS itself that any process could call. So, the shell provides signals when these attributes change, which the PS listens to and pushes out over the network accordingly. --- shell/model/Owner.py | 30 ++++++++++++++++++++++------ shell/shellservice.py | 46 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/shell/model/Owner.py b/shell/model/Owner.py index 9b5ef9b4..760697aa 100644 --- a/shell/model/Owner.py +++ b/shell/model/Owner.py @@ -14,6 +14,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import gobject import os import random import base64 @@ -30,11 +31,24 @@ from model.Invites import Invites PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp" -class ShellOwner(object): +class ShellOwner(gobject.GObject): + __gtype_name__ = "ShellOwner" + + __gsignals__ = { + 'nick-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_STRING])), + 'color-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'icon-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + """Class representing the owner of this machine/instance. This class runs in the shell and serves up the buddy icon and other stuff. It's the server portion of the Owner, paired with the client portion in Buddy.py.""" def __init__(self): + gobject.GObject.__init__(self) + self._nick = profile.get_nick_name() user_dir = env.get_profile_path() @@ -45,12 +59,14 @@ class ShellOwner(object): continue fd = open(os.path.join(user_dir, fname), "r") self._icon = fd.read() - if self._icon: - # Get the icon's hash - import md5, binascii - digest = md5.new(self._icon).digest() - self._icon_hash = util.printable_hash(digest) fd.close() + if not self._icon: + raise RuntimeError("No buddy icon exists") + + # Get the icon's hash + import md5, binascii + digest = md5.new(self._icon).digest() + self._icon_hash = util.printable_hash(digest) break self._pservice = PresenceService.get_instance() @@ -60,6 +76,7 @@ class ShellOwner(object): self._last_activity_update = time.time() self._pending_activity_update_timer = None self._pending_activity_update = None + self._current_activity = None def get_invites(self): return self._invites @@ -94,6 +111,7 @@ class ShellOwner(object): self._last_activity_update = time.time() self._pending_activity_update_timer = None if self._pending_activity_update: + self.emit('current-activity-changed', self._pending_activity_update) logging.debug("*** Updating current activity to %s" % self._pending_activity_update) self._service.set_published_value('curact', dbus.String(self._pending_activity_update)) return False diff --git a/shell/shellservice.py b/shell/shellservice.py index c31e501b..7cf60f0a 100644 --- a/shell/shellservice.py +++ b/shell/shellservice.py @@ -4,18 +4,58 @@ from sugar.activity import bundleregistry _DBUS_SERVICE = "org.laptop.Shell" _DBUS_INTERFACE = "org.laptop.Shell" +_DBUS_OWNER_INTERFACE = "org.laptop.Shell.Owner" _DBUS_PATH = "/org/laptop/Shell" class ShellService(dbus.service.Object): - def __init__(self, shellModel): - self._shellModel = shellModel + def __init__(self, shell_model): + self._shell_model = shell_model + + self._owner = self._shell_model.get_owner() + self._owner.connect('nick-changed', self._owner_nick_changed_cb) + self._owner.connect('icon-changed', self._owner_icon_changed_cb) + self._owner.connect('color-changed', self._owner_color_changed_cb) + + self._home_model = self._shell_model.get_home() + self._home_model.connect('active-activity-changed', self._cur_activity_changed_cb) bus = dbus.SessionBus() bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus) dbus.service.Object.__init__(self, bus_name, _DBUS_PATH) - + @dbus.service.method(_DBUS_INTERFACE, in_signature="s", out_signature="b") def add_bundle(self, bundle_path): registry = bundleregistry.get_registry() return registry.add_bundle(bundle_path) + + @dbus.service.signal(_DBUS_OWNER_INTERFACE, signature="s") + def ColorChanged(self, color): + pass + + def _owner_color_changed_cb(self, new_color): + self.ColorChanged(new_color.to_string()) + + @dbus.service.signal(_DBUS_OWNER_INTERFACE, signature="s") + def NickChanged(self, nick): + pass + + def _owner_nick_changed_cb(self, new_nick): + self.NickChanged(new_nick) + + @dbus.service.signal(_DBUS_OWNER_INTERFACE, signature="ay") + def IconChanged(self, icon_data): + pass + + def _owner_icon_changed_cb(self, new_icon): + self.IconChanged(dbus.ByteArray(new_icon)) + + @dbus.service.signal(_DBUS_OWNER_INTERFACE, signature="s") + def CurrentActivityChanged(self, activity_id): + pass + + def _cur_activity_changed_cb(self, owner, new_activity): + new_id = "" + if new_activity: + new_id = new_activity.get_id() + self.CurrentActivityChanged(new_id)