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:
parent
894fcea9fc
commit
162a87f882
@ -18,9 +18,11 @@
|
||||
import os
|
||||
import gobject
|
||||
import dbus, dbus.service
|
||||
from ConfigParser import ConfigParser, NoOptionError
|
||||
|
||||
from sugar import profile
|
||||
from sugar import env
|
||||
from sugar import env, profile, util
|
||||
import logging
|
||||
import random
|
||||
|
||||
_BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/"
|
||||
_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
|
||||
activities and resources they make available for sharing."""
|
||||
|
||||
__gtype_name__ = "Buddy"
|
||||
|
||||
__gsignals__ = {
|
||||
'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_BOOLEAN])),
|
||||
@ -246,16 +246,58 @@ class Buddy(DBusGObject):
|
||||
except AttributeError:
|
||||
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
|
||||
portion of the Owner, paired with the server portion in Owner.py."""
|
||||
|
||||
__gtype_name__ = "ShellOwner"
|
||||
|
||||
_SHELL_SERVICE = "org.laptop.Shell"
|
||||
_SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner"
|
||||
_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()
|
||||
nick = profile.get_nick_name()
|
||||
color = profile.get_color().to_string()
|
||||
@ -265,10 +307,14 @@ class Owner(Buddy):
|
||||
icon = f.read()
|
||||
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.add_signal_receiver(self._name_owner_changed_handler,
|
||||
signal_name="NameOwnerChanged",
|
||||
dbus_interface="org.freedesktop.DBus")
|
||||
signal_name="NameOwnerChanged",
|
||||
dbus_interface="org.freedesktop.DBus")
|
||||
|
||||
# Connect to the shell to get notifications on Owner object
|
||||
# property changes
|
||||
@ -277,36 +323,9 @@ class Owner(Buddy):
|
||||
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 set_registered(self, value):
|
||||
if value:
|
||||
profile.set_server_registered()
|
||||
|
||||
def _name_owner_changed_handler(self, name, old, new):
|
||||
if name != self._SHELL_SERVICE:
|
||||
@ -346,8 +365,204 @@ class Owner(Buddy):
|
||||
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
|
||||
|
||||
def rand():
|
||||
|
@ -25,7 +25,7 @@ from server_plugin import ServerPlugin
|
||||
from linklocal_plugin import LinkLocalPlugin
|
||||
from sugar import util
|
||||
|
||||
from buddy import Buddy, Owner
|
||||
from buddy import Buddy, ShellOwner, TestOwner
|
||||
from activity import Activity
|
||||
|
||||
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
|
||||
@ -40,7 +40,7 @@ class NotFoundError(dbus.DBusException):
|
||||
|
||||
|
||||
class PresenceService(dbus.service.Object):
|
||||
def __init__(self):
|
||||
def __init__(self, test=0):
|
||||
self._next_object_id = 0
|
||||
|
||||
self._buddies = {} # key -> Buddy
|
||||
@ -52,7 +52,10 @@ class PresenceService(dbus.service.Object):
|
||||
|
||||
# Create the Owner object
|
||||
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._registry = ManagerRegistry()
|
||||
@ -326,9 +329,9 @@ class PresenceService(dbus.service.Object):
|
||||
activity.set_properties(props)
|
||||
|
||||
|
||||
def main():
|
||||
def main(test=False):
|
||||
loop = gobject.MainLoop()
|
||||
ps = PresenceService()
|
||||
ps = PresenceService(test)
|
||||
try:
|
||||
loop.run()
|
||||
except KeyboardInterrupt:
|
||||
|
@ -17,9 +17,7 @@
|
||||
|
||||
import gobject
|
||||
import dbus
|
||||
from sugar import profile
|
||||
from sugar import util
|
||||
from sugar import env
|
||||
import gtk
|
||||
from buddyiconcache import BuddyIconCache
|
||||
import logging
|
||||
@ -131,21 +129,15 @@ class ServerPlugin(gobject.GObject):
|
||||
def _get_account_info(self):
|
||||
account_info = {}
|
||||
|
||||
pubkey = self._owner.props.key
|
||||
account_info['server'] = self._owner.get_server()
|
||||
|
||||
server = profile.get_server()
|
||||
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))
|
||||
khash = util.printable_hash(util._sha_data(self._owner.props.key))
|
||||
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
|
||||
|
||||
def _find_existing_connection(self):
|
||||
@ -208,7 +200,7 @@ class ServerPlugin(gobject.GObject):
|
||||
def _connected_cb(self):
|
||||
if self._account['register']:
|
||||
# we successfully register this account
|
||||
profile.set_server_registered()
|
||||
self._owner.props.registered = True
|
||||
|
||||
# the group of contacts who may receive your presence
|
||||
publish = self._request_list_channel('publish')
|
||||
@ -388,21 +380,27 @@ class ServerPlugin(gobject.GObject):
|
||||
self._conn[CONN_INTERFACE].Disconnect()
|
||||
|
||||
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]
|
||||
|
||||
def _contact_online_activities_cb(self, handle, activities):
|
||||
if not activities or not len(activities):
|
||||
logging.debug("Handle %s - No activities" % handle)
|
||||
self._contact_offline(handle)
|
||||
return
|
||||
self._buddy_activities_changed_cb(handle, activities)
|
||||
|
||||
def _contact_online_activities_error_cb(self, 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):
|
||||
if not aliases or not len(aliases):
|
||||
logging.debug("Handle %s - No aliases" % handle)
|
||||
self._contact_offline(handle)
|
||||
return
|
||||
|
||||
props['nick'] = aliases[0]
|
||||
@ -416,12 +414,17 @@ class ServerPlugin(gobject.GObject):
|
||||
|
||||
def _contact_online_aliases_error_cb(self, handle, err):
|
||||
logging.debug("Handle %s - Error getting nickname: %s" % (handle, err))
|
||||
self._contact_offline(handle)
|
||||
|
||||
def _contact_online_properties_cb(self, handle, props):
|
||||
if not props.has_key('key'):
|
||||
logging.debug("Handle %s - invalid key." % handle)
|
||||
self._contact_offline(handle)
|
||||
return
|
||||
if not props.has_key('color'):
|
||||
logging.debug("Handle %s - invalid color." % handle)
|
||||
self._contact_offline(handle)
|
||||
return
|
||||
|
||||
# Convert key from dbus byte array to python string
|
||||
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):
|
||||
logging.debug("Handle %s - Error getting properties: %s" % (handle, err))
|
||||
self._contact_offline(handle)
|
||||
|
||||
def _contact_online(self, handle):
|
||||
self._online_contacts[handle] = None
|
||||
self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle,
|
||||
reply_handler=lambda *args: self._contact_online_properties_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))
|
||||
if not online and status in ["available", "away", "brb", "busy", "dnd", "xa"]:
|
||||
self._contact_online(handle)
|
||||
elif online and status in ["offline", "invisible"]:
|
||||
elif status in ["offline", "invisible"]:
|
||||
self._contact_offline(handle)
|
||||
|
||||
def _avatar_updated_cb(self, handle, new_avatar_token):
|
||||
@ -459,6 +464,10 @@ class ServerPlugin(gobject.GObject):
|
||||
return
|
||||
|
||||
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)
|
||||
if not icon:
|
||||
# cache miss
|
||||
@ -472,20 +481,24 @@ class ServerPlugin(gobject.GObject):
|
||||
for handle, alias in aliases:
|
||||
prop = {'nick': 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):
|
||||
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
|
||||
# ignore network events for Owner property changes since those
|
||||
# are handled locally
|
||||
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):
|
||||
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
|
||||
# ignore network events for Owner activity changes since those
|
||||
# are handled locally
|
||||
return
|
||||
if not self._online_contacts.has_key(handle) or not self._online_contacts[handle]:
|
||||
return
|
||||
|
||||
for act_id, act_handle in activities:
|
||||
self._activities[act_id] = act_handle
|
||||
@ -497,6 +510,8 @@ class ServerPlugin(gobject.GObject):
|
||||
# ignore network events for Owner current activity changes since those
|
||||
# are handled locally
|
||||
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):
|
||||
activity = None
|
||||
|
@ -32,4 +32,13 @@ import presenceservice
|
||||
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user