diff --git a/services/presence2/activity.py b/services/presence2/activity.py index 3a4216c8..656bb18f 100644 --- a/services/presence2/activity.py +++ b/services/presence2/activity.py @@ -15,32 +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 +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, @@ -62,12 +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.props.type @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="") @@ -77,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, @@ -89,41 +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 + 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 afccbbde..2018ec1c 100644 --- a/services/presence2/presenceservice.py +++ b/services/presence2/presenceservice.py @@ -134,25 +134,38 @@ 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() - 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() diff --git a/services/presence2/server_plugin.py b/services/presence2/server_plugin.py index 5ce46b02..3c0986c2 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() @@ -406,9 +404,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): 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)