diff options
Diffstat (limited to 'services/presence/buddy.py')
-rw-r--r-- | services/presence/buddy.py | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/services/presence/buddy.py b/services/presence/buddy.py new file mode 100644 index 0000000..4b6031d --- /dev/null +++ b/services/presence/buddy.py @@ -0,0 +1,396 @@ +# Copyright (C) 2007, Red Hat, Inc. +# Copyright (C) 2007, Collabora Ltd. +# +# 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 +import gobject +import dbus, dbus.service + +from sugar import profile +from sugar import env + +_BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/" +_BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy" +_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner" + +class NotFoundError(dbus.DBusException): + def __init__(self): + dbus.DBusException.__init__(self) + self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound' + +class DBusGObjectMetaclass(gobject.GObjectMeta, dbus.service.InterfaceType): pass +class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass + + +class Buddy(DBusGObject): + """Represents another person on the network and keeps track of the + activities and resources they make available for sharing.""" + + __gtype_name__ = "Buddy" + + __gsignals__ = { + 'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_BOOLEAN])), + 'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'icon-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + __gproperties__ = { + 'key' : (str, None, None, None, + gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY), + 'icon' : (object, None, None, gobject.PARAM_READWRITE), + 'nick' : (str, None, None, None, gobject.PARAM_READWRITE), + 'color' : (str, None, None, None, gobject.PARAM_READWRITE), + 'current-activity' : (str, None, None, None, gobject.PARAM_READWRITE), + 'valid' : (bool, None, None, False, gobject.PARAM_READABLE), + 'owner' : (bool, None, None, False, gobject.PARAM_READABLE) + } + + def __init__(self, bus_name, object_id, **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") + + self._bus_name = bus_name + self._object_id = object_id + self._object_path = _BUDDY_PATH + str(self._object_id) + dbus.service.Object.__init__(self, self._bus_name, self._object_path) + + self._activities = {} # Activity ID -> Activity + self.handles = {} # tp client -> handle + + self._valid = False + self._owner = False + self._key = None + self._icon = '' + self._current_activity = None + self._nick = None + self._color = None + + if not kwargs.get("key"): + raise ValueError("key required") + + gobject.GObject.__init__(self, **kwargs) + + def do_get_property(self, pspec): + if pspec.name == "key": + return self._key + elif pspec.name == "icon": + return self._icon + elif pspec.name == "nick": + return self._nick + elif pspec.name == "color": + return self._color + elif pspec.name == "current-activity": + if not self._current_activity: + return None + if not self._activities.has_key(self._current_activity): + return None + return self._current_activity + elif pspec.name == "valid": + return self._valid + elif pspec.name == "owner": + return self._owner + + def do_set_property(self, pspec, value): + if pspec.name == "icon": + if str(value) != self._icon: + self._icon = str(value) + self.IconChanged(self._icon) + self.emit('icon-changed', self._icon) + elif pspec.name == "nick": + self._nick = value + elif pspec.name == "color": + self._color = value + elif pspec.name == "current-activity": + self._current_activity = value + elif pspec.name == "key": + self._key = value + + self._update_validity() + + # dbus signals + @dbus.service.signal(_BUDDY_INTERFACE, + signature="ay") + def IconChanged(self, icon_data): + pass + + @dbus.service.signal(_BUDDY_INTERFACE, + signature="o") + def JoinedActivity(self, activity_path): + pass + + @dbus.service.signal(_BUDDY_INTERFACE, + signature="o") + def LeftActivity(self, activity_path): + pass + + @dbus.service.signal(_BUDDY_INTERFACE, + signature="a{sv}") + def PropertyChanged(self, updated): + pass + + # dbus methods + @dbus.service.method(_BUDDY_INTERFACE, + in_signature="", out_signature="ay") + def GetIcon(self): + if not self.props.icon: + return "" + return dbus.ByteArray(self.props.icon) + + @dbus.service.method(_BUDDY_INTERFACE, + in_signature="", out_signature="ao") + def GetJoinedActivities(self): + acts = [] + for act in self.get_joined_activities(): + acts.append(act.object_path()) + return acts + + @dbus.service.method(_BUDDY_INTERFACE, + in_signature="", out_signature="a{sv}") + def GetProperties(self): + props = {} + props['nick'] = self.props.nick + props['owner'] = self.props.owner + props['key'] = self.props.key + props['color'] = self.props.color + return props + + # methods + def object_path(self): + return dbus.ObjectPath(self._object_path) + + def add_activity(self, activity): + actid = activity.props.id + if self._activities.has_key(actid): + return + self._activities[actid] = activity + if activity.props.valid: + self.JoinedActivity(activity.object_path()) + + def remove_activity(self, activity): + actid = activity.props.id + if not self._activities.has_key(actid): + return + del self._activities[actid] + if activity.props.valid: + self.LeftActivity(activity.object_path()) + + def get_joined_activities(self): + acts = [] + for act in self._activities.values(): + if act.props.valid: + acts.append(act) + return acts + + def set_properties(self, properties): + changed = False + if "nick" in properties.keys(): + nick = properties["nick"] + if nick != self._nick: + self._nick = nick + changed = True + if "color" in properties.keys(): + color = properties["color"] + if color != self._color: + self._color = color + changed = True + if "current-activity" in properties.keys(): + curact = properties["current-activity"] + if curact != self._current_activity: + self._current_activity = curact + changed = True + + if not changed: + return + + # Try emitting PropertyChanged before updating validity + # to avoid leaking a PropertyChanged signal before the buddy is + # actually valid the first time after creation + if self._valid: + self.PropertyChanged(properties) + self.emit('property-changed', properties) + + self._update_validity() + + def _update_validity(self): + try: + old_valid = self._valid + if self._color and self._nick and self._key: + self._valid = True + else: + self._valid = False + + if old_valid != self._valid: + self.emit("validity-changed", self._valid) + except AttributeError: + self._valid = 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.""" + + _SHELL_SERVICE = "org.laptop.Shell" + _SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner" + _SHELL_PATH = "/org/laptop/Shell" + + def __init__(self, bus_name, object_id): + key = profile.get_pubkey() + nick = profile.get_nick_name() + color = profile.get_color().to_string() + + icon_file = os.path.join(env.get_profile_path(), "buddy-icon.jpg") + f = open(icon_file, "r") + icon = f.read() + f.close() + + self._bus = dbus.SessionBus() + self._bus.add_signal_receiver(self._name_owner_changed_handler, + signal_name="NameOwnerChanged", + dbus_interface="org.freedesktop.DBus") + + # Connect to the shell to get notifications on Owner object + # property changes + try: + self._connect_to_shell() + except dbus.DBusException: + pass + + Buddy.__init__(self, bus_name, object_id, key=key, nick=nick, color=color, + icon=icon) + self._owner = True + + # enable this to change random buddy properties + if False: + gobject.timeout_add(5000, self._update_something) + + def _update_something(self): + import random + it = random.randint(0, 3) + if it == 0: + data = get_random_image() + self._icon_changed_cb(data) + elif it == 1: + from sugar.graphics import xocolor + xo = xocolor.XoColor() + self._color_changed_cb(xo.to_string()) + elif it == 2: + names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"] + foo = random.randint(0, len(names) - 1) + self._nick_changed_cb(names[foo]) + elif it == 3: + bork = random.randint(25, 65) + it = "" + for i in range(0, bork): + it += chr(random.randint(40, 127)) + from sugar import util + self._cur_activity_changed_cb(util.unique_id(it)) + return True + + def _name_owner_changed_handler(self, name, old, new): + if name != self._SHELL_SERVICE: + return + if (old and len(old)) and (not new and not len(new)): + # shell went away + self._shell_owner = None + elif (not old and not len(old)) and (new and len(new)): + # shell started + self._connect_to_shell() + + def _connect_to_shell(self): + obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH) + 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('ColorChanged', self._color_changed_cb) + self._shell_owner.connect_to_signal('NickChanged', self._nick_changed_cb) + self._shell_owner.connect_to_signal('CurrentActivityChanged', + self._cur_activity_changed_cb) + + def _icon_changed_cb(self, icon): + self.props.icon = icon + + def _color_changed_cb(self, color): + props = {'color': color} + self.set_properties(props) + + def _nick_changed_cb(self, nick): + props = {'nick': nick} + self.set_properties(props) + + def _cur_activity_changed_cb(self, activity_id): + if not self._activities.has_key(activity_id): + # This activity is local-only + activity_id = None + props = {'current-activity': activity_id} + self.set_properties(props) + + + +def get_random_image(): + import cairo, math, random, gtk + + def rand(): + return random.random() + + SIZE = 200 + + s = cairo.ImageSurface(cairo.FORMAT_ARGB32, SIZE, SIZE) + cr = cairo.Context(s) + + # background gradient + cr.save() + g = cairo.LinearGradient(0, 0, 1, 1) + g.add_color_stop_rgba(1, rand(), rand(), rand(), rand()) + g.add_color_stop_rgba(0, rand(), rand(), rand(), rand()) + cr.set_source(g) + cr.rectangle(0, 0, SIZE, SIZE); + cr.fill() + cr.restore() + + # random path + cr.set_line_width(10 * rand() + 5) + cr.move_to(SIZE * rand(), SIZE * rand()) + cr.line_to(SIZE * rand(), SIZE * rand()) + cr.rel_line_to(SIZE * rand() * -1, 0) + cr.close_path() + cr.stroke() + + # a circle + cr.set_source_rgba(rand(), rand(), rand(), rand()) + cr.arc(SIZE * rand(), SIZE * rand(), 100 * rand() + 30, 0, 2 * math.pi) + cr.fill() + + # another circle + cr.set_source_rgba(rand(), rand(), rand(), rand()) + cr.arc(SIZE * rand(), SIZE * rand(), 100 * rand() + 30, 0, 2 * math.pi) + cr.fill() + + def img_convert_func(buf, data): + data[0] += buf + return True + + data = [""] + pixbuf = gtk.gdk.pixbuf_new_from_data(s.get_data(), gtk.gdk.COLORSPACE_RGB, + True, 8, s.get_width(), s.get_height(), s.get_stride()) + pixbuf.save_to_callback(img_convert_func, "jpeg", {"quality": "90"}, data) + del pixbuf + + return str(data[0]) + |