Create test owner for presence service

This owner changes properties periodically so we can test out the PS's handling
of property changes.  To execute, run Sugar and then make sure that the D-Bus
address is the one sugar is using (get it from ~/.sugar/default/session.info).
Then run:

build/bin/sugar-presence-service X

where X is a number 1 -> 9 inclusive.  It will generate fake buddy info for that
test buddy and then start up a presence service for that buddy, changing a random
property of the buddy every 10 seconds.
This commit is contained in:
Dan Williams 2007-04-11 22:49:14 -04:00
parent 894fcea9fc
commit 162a87f882
4 changed files with 306 additions and 64 deletions

View File

@ -18,9 +18,11 @@
import os import os
import gobject import gobject
import dbus, dbus.service import dbus, dbus.service
from ConfigParser import ConfigParser, NoOptionError
from sugar import profile from sugar import env, profile, util
from sugar import env import logging
import random
_BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/" _BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/"
_BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy" _BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
@ -39,8 +41,6 @@ class Buddy(DBusGObject):
"""Represents another person on the network and keeps track of the """Represents another person on the network and keeps track of the
activities and resources they make available for sharing.""" activities and resources they make available for sharing."""
__gtype_name__ = "Buddy"
__gsignals__ = { __gsignals__ = {
'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_BOOLEAN])), ([gobject.TYPE_BOOLEAN])),
@ -246,16 +246,58 @@ class Buddy(DBusGObject):
except AttributeError: except AttributeError:
self._valid = False self._valid = False
class GenericOwner(Buddy):
__gtype_name__ = "GenericOwner"
class Owner(Buddy): __gproperties__ = {
'registered' : (bool, None, None, False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
'server' : (str, None, None, None, gobject.PARAM_READABLE | gobject.PARAM_CONSTRUCT),
'key-hash' : (str, None, None, None, gobject.PARAM_READABLE | gobject.PARAM_CONSTRUCT)
}
def __init__(self, bus_name, object_id, **kwargs):
self._server = 'olpc.collabora.co.uk'
self._key_hash = None
self._registered = False
if kwargs.has_key("server"):
self._server = kwargs["server"]
del kwargs["server"]
if kwargs.has_key("key_hash"):
self._key_hash = kwargs["key_hash"]
del kwargs["key_hash"]
if kwargs.has_key("registered"):
self._registered = kwargs["registered"]
del kwargs["registered"]
Buddy.__init__(self, bus_name, object_id, **kwargs)
self._owner = True
def get_registered(self):
return self._registered
def get_server(self):
return self._server
def get_key_hash(self):
return self._key_hash
def set_registered(self, registered):
raise RuntimeError("Subclasses must implement")
class ShellOwner(GenericOwner):
"""Class representing the owner of the machine. This is the client """Class representing the owner of the machine. This is the client
portion of the Owner, paired with the server portion in Owner.py.""" portion of the Owner, paired with the server portion in Owner.py."""
__gtype_name__ = "ShellOwner"
_SHELL_SERVICE = "org.laptop.Shell" _SHELL_SERVICE = "org.laptop.Shell"
_SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner" _SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner"
_SHELL_PATH = "/org/laptop/Shell" _SHELL_PATH = "/org/laptop/Shell"
def __init__(self, bus_name, object_id): def __init__(self, bus_name, object_id, test=False):
server = profile.get_server()
key_hash = profile.get_private_key_hash()
registered = profile.get_server_registered()
key = profile.get_pubkey() key = profile.get_pubkey()
nick = profile.get_nick_name() nick = profile.get_nick_name()
color = profile.get_color().to_string() color = profile.get_color().to_string()
@ -265,10 +307,14 @@ class Owner(Buddy):
icon = f.read() icon = f.read()
f.close() f.close()
GenericOwner.__init__(self, bus_name, object_id, key=key, nick=nick,
color=color, icon=icon, server=server, key_hash=key_hash,
registered=registered)
self._bus = dbus.SessionBus() self._bus = dbus.SessionBus()
self._bus.add_signal_receiver(self._name_owner_changed_handler, self._bus.add_signal_receiver(self._name_owner_changed_handler,
signal_name="NameOwnerChanged", signal_name="NameOwnerChanged",
dbus_interface="org.freedesktop.DBus") dbus_interface="org.freedesktop.DBus")
# Connect to the shell to get notifications on Owner object # Connect to the shell to get notifications on Owner object
# property changes # property changes
@ -277,36 +323,9 @@ class Owner(Buddy):
except dbus.DBusException: except dbus.DBusException:
pass pass
Buddy.__init__(self, bus_name, object_id, key=key, nick=nick, color=color, def set_registered(self, value):
icon=icon) if value:
self._owner = True profile.set_server_registered()
# 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): def _name_owner_changed_handler(self, name, old, new):
if name != self._SHELL_SERVICE: if name != self._SHELL_SERVICE:
@ -346,8 +365,204 @@ class Owner(Buddy):
self.set_properties(props) self.set_properties(props)
class TestOwner(GenericOwner):
"""Class representing the owner of the machine. This test owner
changes random attributes periodically."""
def get_random_image(): __gtype_name__ = "TestOwner"
def __init__(self, bus_name, object_id, test_num):
self._cp = ConfigParser()
self._section = "Info"
self._cfg_file = os.path.join(env.get_profile_path(), 'test-buddy-%d' % test_num)
(pubkey, privkey, registered) = self._load_config()
if not pubkey or not len(pubkey) or not privkey or not len(privkey):
(pubkey, privkey) = _get_new_keypair(test_num)
if not pubkey or not privkey:
raise RuntimeError("Couldn't get or create test buddy keypair")
self._save_config(pubkey, privkey, registered)
privkey_hash = util.printable_hash(util._sha_data(privkey))
nick = _get_random_name()
from sugar.graphics import xocolor
color = xocolor.XoColor().to_string()
icon = _get_random_image()
GenericOwner.__init__(self, bus_name, object_id, key=pubkey, nick=nick,
color=color, icon=icon, registered=registered, key_hash=privkey_hash)
# Change a random property ever 10 seconds
gobject.timeout_add(10000, self._update_something)
def set_registered(self, value):
if value:
self._registered = True
def _load_config(self):
if not os.path.exists(self._cfg_file):
return (None, None, False)
if not self._cp.read([self._cfg_file]):
return (None, None, False)
if not self._cp.has_section(self._section):
return (None, None, False)
try:
pubkey = self._cp.get(self._section, "pubkey")
privkey = self._cp.get(self._section, "privkey")
registered = self._cp.get(self._section, "registered")
return (pubkey, privkey, registered)
except NoOptionError:
pass
return (None, None, False)
def _save_config(self, pubkey, privkey, registered):
# Save config again
if not self._cp.has_section(self._section):
self._cp.add_section(self._section)
self._cp.set(self._section, "pubkey", pubkey)
self._cp.set(self._section, "privkey", privkey)
self._cp.set(self._section, "registered", registered)
f = open(self._cfg_file, 'w')
self._cp.write(f)
f.close()
def _update_something(self):
it = random.randint(0, 3)
if it == 0:
self.props.icon = _get_random_image()
elif it == 1:
from sugar.graphics import xocolor
props = {'color': xocolor.XoColor().to_string()}
self.set_properties(props)
elif it == 2:
props = {'nick': _get_random_name()}
self.set_properties(props)
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
props = {'current-activity': util.unique_id(it)}
self.set_properties(props)
return True
def _hash_private_key(self):
self.privkey_hash = None
key_path = os.path.join(env.get_profile_path(), 'owner.key')
try:
f = open(key_path, "r")
lines = f.readlines()
f.close()
except IOError, e:
logging.error("Error reading private key: %s" % e)
return
key = ""
for l in lines:
l = l.strip()
if l.startswith("-----BEGIN DSA PRIVATE KEY-----"):
continue
if l.startswith("-----END DSA PRIVATE KEY-----"):
continue
key += l
if not len(key):
logging.error("Error parsing public key.")
# hash it
key_hash = util._sha_data(key)
self.privkey_hash = util.printable_hash(key_hash)
def _extract_public_key(keyfile):
try:
f = open(keyfile, "r")
lines = f.readlines()
f.close()
except IOError, e:
logging.error("Error reading public key: %s" % e)
return None
# Extract the public key
magic = "ssh-dss "
key = ""
for l in lines:
l = l.strip()
if not l.startswith(magic):
continue
key = l[len(magic):]
break
if not len(key):
logging.error("Error parsing public key.")
return None
return key
def _extract_private_key(keyfile):
# Extract the private key
try:
f = open(keyfile, "r")
lines = f.readlines()
f.close()
except IOError, e:
logging.error("Error reading private key: %s" % e)
return None
key = ""
for l in lines:
l = l.strip()
if l.startswith("-----BEGIN DSA PRIVATE KEY-----"):
continue
if l.startswith("-----END DSA PRIVATE KEY-----"):
continue
key += l
if not len(key):
logging.error("Error parsing private key.")
return None
return key
def _get_new_keypair(num):
# Generate keypair
privkeyfile = os.path.join("/tmp", "test%d.key" % num)
pubkeyfile = os.path.join("/tmp", 'test%d.key.pub' % num)
# force-remove key files if they exist to ssh-keygen doesn't
# start asking questions
try:
os.remove(pubkeyfile)
os.remove(privkeyfile)
except OSError:
pass
cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % privkeyfile
import commands
print "Generating new keypair..."
(s, o) = commands.getstatusoutput(cmd)
print "Done."
pubkey = privkey = None
if s != 0:
logging.error("Could not generate key pair: %d (%s)" % (s, o))
else:
pubkey = _extract_public_key(pubkeyfile)
privkey = _extract_private_key(privkeyfile)
try:
os.remove(pubkeyfile)
os.remove(privkeyfile)
except OSError:
pass
return (pubkey, privkey)
def _get_random_name():
names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"]
return names[random.randint(0, len(names) - 1)]
def _get_random_image():
import cairo, math, random, gtk import cairo, math, random, gtk
def rand(): def rand():

View File

@ -25,7 +25,7 @@ from server_plugin import ServerPlugin
from linklocal_plugin import LinkLocalPlugin from linklocal_plugin import LinkLocalPlugin
from sugar import util from sugar import util
from buddy import Buddy, Owner from buddy import Buddy, ShellOwner, TestOwner
from activity import Activity from activity import Activity
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence" _PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
@ -40,7 +40,7 @@ class NotFoundError(dbus.DBusException):
class PresenceService(dbus.service.Object): class PresenceService(dbus.service.Object):
def __init__(self): def __init__(self, test=0):
self._next_object_id = 0 self._next_object_id = 0
self._buddies = {} # key -> Buddy self._buddies = {} # key -> Buddy
@ -52,7 +52,10 @@ class PresenceService(dbus.service.Object):
# Create the Owner object # Create the Owner object
objid = self._get_next_object_id() objid = self._get_next_object_id()
self._owner = Owner(self._bus_name, objid) if test > 0:
self._owner = TestOwner(self._bus_name, objid, test)
else:
self._owner = ShellOwner(self._bus_name, objid)
self._buddies[self._owner.props.key] = self._owner self._buddies[self._owner.props.key] = self._owner
self._registry = ManagerRegistry() self._registry = ManagerRegistry()
@ -326,9 +329,9 @@ class PresenceService(dbus.service.Object):
activity.set_properties(props) activity.set_properties(props)
def main(): def main(test=False):
loop = gobject.MainLoop() loop = gobject.MainLoop()
ps = PresenceService() ps = PresenceService(test)
try: try:
loop.run() loop.run()
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@ -17,9 +17,7 @@
import gobject import gobject
import dbus import dbus
from sugar import profile
from sugar import util from sugar import util
from sugar import env
import gtk import gtk
from buddyiconcache import BuddyIconCache from buddyiconcache import BuddyIconCache
import logging import logging
@ -131,21 +129,15 @@ class ServerPlugin(gobject.GObject):
def _get_account_info(self): def _get_account_info(self):
account_info = {} account_info = {}
pubkey = self._owner.props.key account_info['server'] = self._owner.get_server()
server = profile.get_server() khash = util.printable_hash(util._sha_data(self._owner.props.key))
if not server:
account_info['server'] = 'olpc.collabora.co.uk'
else:
account_info['server'] = server
registered = profile.get_server_registered()
account_info['register'] = not registered
khash = util.printable_hash(util._sha_data(pubkey))
account_info['account'] = "%s@%s" % (khash, account_info['server']) account_info['account'] = "%s@%s" % (khash, account_info['server'])
account_info['password'] = profile.get_private_key_hash() account_info['password'] = self._owner.get_key_hash()
account_info['register'] = not self._owner.get_registered()
print "ACCT: %s" % account_info
return account_info return account_info
def _find_existing_connection(self): def _find_existing_connection(self):
@ -208,7 +200,7 @@ class ServerPlugin(gobject.GObject):
def _connected_cb(self): def _connected_cb(self):
if self._account['register']: if self._account['register']:
# we successfully register this account # we successfully register this account
profile.set_server_registered() self._owner.props.registered = True
# the group of contacts who may receive your presence # the group of contacts who may receive your presence
publish = self._request_list_channel('publish') publish = self._request_list_channel('publish')
@ -388,21 +380,27 @@ class ServerPlugin(gobject.GObject):
self._conn[CONN_INTERFACE].Disconnect() self._conn[CONN_INTERFACE].Disconnect()
def _contact_offline(self, handle): def _contact_offline(self, handle):
self.emit("contact-offline", handle) if not self._online_contacts.has_key(handle):
return
if self._online_contacts[handle]:
self.emit("contact-offline", handle)
del self._online_contacts[handle] del self._online_contacts[handle]
def _contact_online_activities_cb(self, handle, activities): def _contact_online_activities_cb(self, handle, activities):
if not activities or not len(activities): if not activities or not len(activities):
logging.debug("Handle %s - No activities" % handle) logging.debug("Handle %s - No activities" % handle)
self._contact_offline(handle)
return return
self._buddy_activities_changed_cb(handle, activities) self._buddy_activities_changed_cb(handle, activities)
def _contact_online_activities_error_cb(self, handle, err): def _contact_online_activities_error_cb(self, handle, err):
logging.debug("Handle %s - Error getting activities: %s" % (handle, err)) logging.debug("Handle %s - Error getting activities: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online_aliases_cb(self, handle, props, aliases): def _contact_online_aliases_cb(self, handle, props, aliases):
if not aliases or not len(aliases): if not aliases or not len(aliases):
logging.debug("Handle %s - No aliases" % handle) logging.debug("Handle %s - No aliases" % handle)
self._contact_offline(handle)
return return
props['nick'] = aliases[0] props['nick'] = aliases[0]
@ -416,12 +414,17 @@ class ServerPlugin(gobject.GObject):
def _contact_online_aliases_error_cb(self, handle, err): def _contact_online_aliases_error_cb(self, handle, err):
logging.debug("Handle %s - Error getting nickname: %s" % (handle, err)) logging.debug("Handle %s - Error getting nickname: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online_properties_cb(self, handle, props): def _contact_online_properties_cb(self, handle, props):
if not props.has_key('key'): if not props.has_key('key'):
logging.debug("Handle %s - invalid key." % handle) logging.debug("Handle %s - invalid key." % handle)
self._contact_offline(handle)
return
if not props.has_key('color'): if not props.has_key('color'):
logging.debug("Handle %s - invalid color." % handle) logging.debug("Handle %s - invalid color." % handle)
self._contact_offline(handle)
return
# Convert key from dbus byte array to python string # Convert key from dbus byte array to python string
props["key"] = psutils.bytes_to_string(props["key"]) props["key"] = psutils.bytes_to_string(props["key"])
@ -432,8 +435,10 @@ class ServerPlugin(gobject.GObject):
def _contact_online_properties_error_cb(self, handle, err): def _contact_online_properties_error_cb(self, handle, err):
logging.debug("Handle %s - Error getting properties: %s" % (handle, err)) logging.debug("Handle %s - Error getting properties: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online(self, handle): def _contact_online(self, handle):
self._online_contacts[handle] = None
self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle, self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle,
reply_handler=lambda *args: self._contact_online_properties_cb(handle, *args), reply_handler=lambda *args: self._contact_online_properties_cb(handle, *args),
error_handler=lambda *args: self._contact_online_properties_error_cb(handle, *args)) error_handler=lambda *args: self._contact_online_properties_error_cb(handle, *args))
@ -449,7 +454,7 @@ class ServerPlugin(gobject.GObject):
logging.debug("Handle %s (%s) was %s, status now '%s'." % (handle, jid, olstr, status)) logging.debug("Handle %s (%s) was %s, status now '%s'." % (handle, jid, olstr, status))
if not online and status in ["available", "away", "brb", "busy", "dnd", "xa"]: if not online and status in ["available", "away", "brb", "busy", "dnd", "xa"]:
self._contact_online(handle) self._contact_online(handle)
elif online and status in ["offline", "invisible"]: elif status in ["offline", "invisible"]:
self._contact_offline(handle) self._contact_offline(handle)
def _avatar_updated_cb(self, handle, new_avatar_token): def _avatar_updated_cb(self, handle, new_avatar_token):
@ -459,6 +464,10 @@ class ServerPlugin(gobject.GObject):
return return
jid = self._online_contacts[handle] jid = self._online_contacts[handle]
if not jid:
logging.debug("Handle %s not valid yet...")
return
icon = self._icon_cache.get_icon(jid, new_avatar_token) icon = self._icon_cache.get_icon(jid, new_avatar_token)
if not icon: if not icon:
# cache miss # cache miss
@ -472,20 +481,24 @@ class ServerPlugin(gobject.GObject):
for handle, alias in aliases: for handle, alias in aliases:
prop = {'nick': alias} prop = {'nick': alias}
#print "Buddy %s alias changed to %s" % (handle, alias) #print "Buddy %s alias changed to %s" % (handle, alias)
self._buddy_properties_changed_cb(handle, prop) if self._online_contacts.has_key(handle) and self._online_contacts[handle]:
self._buddy_properties_changed_cb(handle, prop)
def _buddy_properties_changed_cb(self, handle, properties): def _buddy_properties_changed_cb(self, handle, properties):
if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner property changes since those # ignore network events for Owner property changes since those
# are handled locally # are handled locally
return return
self.emit("buddy-properties-changed", handle, properties) if self._online_contacts.has_key(handle) and self._online_contacts[handle]:
self.emit("buddy-properties-changed", handle, properties)
def _buddy_activities_changed_cb(self, handle, activities): def _buddy_activities_changed_cb(self, handle, activities):
if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner activity changes since those # ignore network events for Owner activity changes since those
# are handled locally # are handled locally
return return
if not self._online_contacts.has_key(handle) or not self._online_contacts[handle]:
return
for act_id, act_handle in activities: for act_id, act_handle in activities:
self._activities[act_id] = act_handle self._activities[act_id] = act_handle
@ -497,6 +510,8 @@ class ServerPlugin(gobject.GObject):
# ignore network events for Owner current activity changes since those # ignore network events for Owner current activity changes since those
# are handled locally # are handled locally
return return
if not self._online_contacts.has_key(handle) or not self._online_contacts[handle]:
return
if not len(activity) or not util.validate_activity_id(activity): if not len(activity) or not util.validate_activity_id(activity):
activity = None activity = None

View File

@ -32,4 +32,13 @@ import presenceservice
logging.info('Starting presence service') logging.info('Starting presence service')
presenceservice.main() test=0
if len(sys.argv) > 1:
try:
test = int(sys.argv[1])
except ValueError:
logging.debug("Bad test user number.")
if test < 1 or test > 10:
logging.debug("Bad test user number.")
presenceservice.main(test)