diff options
Diffstat (limited to 'sugar')
-rw-r--r-- | sugar/__init__.py | 1 | ||||
-rw-r--r-- | sugar/activity/activity.py | 28 | ||||
-rw-r--r-- | sugar/activity/activityfactory.py | 47 | ||||
-rw-r--r-- | sugar/activity/activityfactoryservice.py | 52 | ||||
-rw-r--r-- | sugar/activity/activityhandle.py | 58 | ||||
-rw-r--r-- | sugar/activity/activityservice.py | 15 | ||||
-rw-r--r-- | sugar/activity/bundle.py | 13 | ||||
-rw-r--r-- | sugar/activity/bundleregistry.py | 19 | ||||
-rw-r--r-- | sugar/date.py | 15 | ||||
-rw-r--r-- | sugar/env.py | 1 | ||||
-rw-r--r-- | sugar/logger.py | 1 | ||||
-rw-r--r-- | sugar/profile.py | 25 | ||||
-rw-r--r-- | sugar/util.py | 33 |
13 files changed, 291 insertions, 17 deletions
diff --git a/sugar/__init__.py b/sugar/__init__.py index 238ae96..05d58f0 100644 --- a/sugar/__init__.py +++ b/sugar/__init__.py @@ -1,3 +1,4 @@ +"""OLPC Sugar User Interface""" ZOOM_MESH = 0 ZOOM_FRIENDS = 1 ZOOM_HOME = 2 diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py index d6e363b..fe0780c 100644 --- a/sugar/activity/activity.py +++ b/sugar/activity/activity.py @@ -1,3 +1,8 @@ +"""Base class for Python-coded activities + +This is currently the only reference for what an +activity must do to participate in the Sugar desktop. +""" # Copyright (C) 2006, Red Hat, Inc. # # This library is free software; you can redistribute it and/or @@ -29,6 +34,26 @@ class Activity(Window, gtk.Container): """Base Activity class that all other Activities derive from.""" __gtype_name__ = 'SugarActivity' def __init__(self, handle): + """Initialise the Activity + + handle -- sugar.activity.activityhandle.ActivityHandle + instance providing the activity id and access to the + presence service which *may* provide sharing for this + application + + Side effects: + + Sets the gdk screen DPI setting (resolution) to the + Sugar screen resolution. + + Connects our "destroy" message to our _destroy_cb + method. + + Creates a base gtk.Window within this window. + + Creates an ActivityService (self._bus) servicing + this application. + """ Window.__init__(self) self.connect('destroy', self._destroy_cb) @@ -105,6 +130,7 @@ class Activity(Window, gtk.Container): return False def _destroy_cb(self, window): + """Destroys our ActivityService and sharing service""" if self._bus: del self._bus self._bus = None @@ -112,4 +138,6 @@ class Activity(Window, gtk.Container): self._pservice.unregister_service(self._service) def get_bundle_path(): + """Return the bundle path for the current process' bundle + """ return os.environ['SUGAR_BUNDLE_PATH'] diff --git a/sugar/activity/activityfactory.py b/sugar/activity/activityfactory.py index 222333e..06ab280 100644 --- a/sugar/activity/activityfactory.py +++ b/sugar/activity/activityfactory.py @@ -1,3 +1,4 @@ +"""Shell side object which manages request to start activity""" # Copyright (C) 2006, Red Hat, Inc. # # This library is free software; you can redistribute it and/or @@ -31,6 +32,7 @@ _ACTIVITY_SERVICE_PATH = "/org/laptop/Activity" _ACTIVITY_INTERFACE = "org.laptop.Activity" def create_activity_id(): + """Generate a new, unique ID for this activity""" pservice = presenceservice.get_instance() # create a new unique activity ID @@ -52,6 +54,14 @@ def create_activity_id(): raise RuntimeError("Cannot generate unique activity id.") class ActivityCreationHandler(gobject.GObject): + """Sugar-side activity creation interface + + This object uses a dbus method on the ActivityFactory + service to create the new activity. It generates + GObject events in response to the success/failure of + activity startup using callbacks to the service's + create call. + """ __gsignals__ = { 'success': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), @@ -61,6 +71,29 @@ class ActivityCreationHandler(gobject.GObject): } def __init__(self, service_name, activity_handle): + """Initialise the handler + + service_name -- used to retrieve the activity bundle + from the global BundleRegistry. This is what + determines what activity will be run. + activity_handle -- stores the values which are to + be passed to the service to uniquely identify + the activity to be created and the sharing + service that may or may not be connected with it + + sugar.activity.activityhandle.ActivityHandle instance + + calls the "create" method on the service for this + particular activity type and registers the + _reply_handler and _error_handler methods on that + call's results. + + The specific service which creates new instances of this + particular type of activity is created during the activity + registration process in + sugar.activity.bundleregistry.BundleRegistry which creates + service definition files for each registered bundle type. + """ gobject.GObject.__init__(self) self._service_name = service_name self._activity_handle = activity_handle @@ -75,16 +108,28 @@ class ActivityCreationHandler(gobject.GObject): factory.create(self._activity_handle.get_dict(), reply_handler=self._reply_handler, error_handler=self._error_handler) - def get_activity_id(self): + """Retrieve the unique identity for this activity""" return self._activity_handle.activity_id def _reply_handler(self, xid): + """Reply from service regarding what window was created + + xid -- X windows ID for the window that was just created + + emits the "success" message (on ourselves) + """ logging.debug("Activity created %s (%s)." % (self._activity_handle.activity_id, self._service_name)) self.emit('success') def _error_handler(self, err): + """Reply from service with an error message (exception) + + err -- exception object describing the error + + emits the "error" message (on ourselves) + """ logging.debug("Couldn't create activity %s (%s): %s" % (self._activity_handle.activity_id, self._service_name, err)) self.emit('error', err) diff --git a/sugar/activity/activityfactoryservice.py b/sugar/activity/activityfactoryservice.py index 3142da0..08b5eec 100644 --- a/sugar/activity/activityfactoryservice.py +++ b/sugar/activity/activityfactoryservice.py @@ -35,9 +35,43 @@ gobject.threads_init() dbus.glib.threads_init() class ActivityFactoryService(dbus.service.Object): - """D-Bus service that creates new instances of an activity""" + """D-Bus service that creates instances of Python activities + + The ActivityFactoryService is a dbus service created for + each Python based activity type (that is, each activity + bundle which declares a "class" in its activity.info file, + rather than an "exec"). + + The ActivityFactoryService is the actual process which + instantiates the Python classes for Sugar interfaces. That + is, your Python code runs in the same process as the + ActivityFactoryService itself. + + The "service" process is created at the moment Sugar first + attempts to create an instance of the activity type. It + then remains in memory until the last instance of the + activity type is terminated. + """ def __init__(self, service_name, activity_class): + """Initialize the service to create activities of this type + + service_name -- bundle's service name, this is used + to construct the dbus service name used to access + the created service. + activity_class -- dotted Python class name for the + Activity class which is to be instantiated by + the service. Assumed to be composed of a module + followed by a class. + + if the module specified has a "start" attribute this object + will be called on service initialisation (before first + instance is created). + + if the module specified has a "stop" attribute this object + will be called after every instance exits (note: may be + called multiple times for each time start is called!) + """ self._activities = [] splitted_module = activity_class.rsplit('.', 1) @@ -60,6 +94,12 @@ class ActivityFactoryService(dbus.service.Object): @dbus.service.method("com.redhat.Sugar.ActivityFactory", in_signature="a{ss}") def create(self, handle): + """Create a new instance of this activity + + handle -- sugar.activity.activityhandle.ActivityHandle + compatible dictionary providing the instance-specific + values for the new instance + """ activity_handle = activityhandle.create_from_dict(handle) activity = self._constructor(activity_handle) activity.present() @@ -70,6 +110,16 @@ class ActivityFactoryService(dbus.service.Object): return activity.window.xid def _activity_destroy_cb(self, activity): + """On close of an instance's root window + + Removes the activity from the tracked activities. + + If our implementation module has a stop, calls + that. + + If there are no more tracked activities, closes + the activity. + """ self._activities.remove(activity) if hasattr(self._module, 'stop'): diff --git a/sugar/activity/activityhandle.py b/sugar/activity/activityhandle.py index fee6327..c87fb6b 100644 --- a/sugar/activity/activityhandle.py +++ b/sugar/activity/activityhandle.py @@ -18,13 +18,47 @@ from sugar.presence import presenceservice class ActivityHandle(object): - def __init__(self, activity_id): + """Data structure storing simple activity metadata""" + def __init__( + self, activity_id, pservice_id=None, + object_id=None,uri=None + ): + """Initialise the handle from activity_id + + activity_id -- unique id for the activity to be + created + pservice_id -- identity of the sharing service + for this activity in the PresenceService + object_id -- identity of the journal object + associated with the activity. It was used by + the journal prototype implementation, might + change when we do the real one. + + When you resume an activity from the journal + the object_id will be passed in. It's optional + since new activities does not have an + associated object (yet). + + XXX Not clear how this relates to the activity + id yet, i.e. not sure we really need both. TBF + uri -- URI associated with the activity. Used when + opening an external file or resource in the + activity, rather than a journal object + (downloads stored on the file system for + example or web pages) + """ self.activity_id = activity_id - self.pservice_id = None - self.object_id = None - self.uri = None + self.pservice_id = pservice_id + self.object_id = object_id + self.uri = uri def get_presence_service(self): + """Retrieve the "sharing service" for this activity + + Uses the PresenceService to find any existing dbus + service which provides sharing mechanisms for this + activity. + """ if self.pservice_id: pservice = presenceservice.get_instance() return pservice.get_activity(self.pservice_id) @@ -32,6 +66,7 @@ class ActivityHandle(object): return None def get_dict(self): + """Retrieve our settings as a dictionary""" result = { 'activity_id' : self.activity_id } if self.pservice_id: result['pservice_id'] = self.pservice_id @@ -43,12 +78,11 @@ class ActivityHandle(object): return result def create_from_dict(handle_dict): - result = ActivityHandle(handle_dict['activity_id']) - if handle_dict.has_key('pservice_id'): - result.pservice_id = handle_dict['pservice_id'] - if handle_dict.has_key('object_id'): - result.object_id = handle_dict['object_id'] - if handle_dict.has_key('uri'): - result.uri = handle_dict['uri'] - + """Create a handle from a dictionary of parameters""" + result = ActivityHandle( + handle_dict['activity_id'], + pservice_id = handle_dict.get( 'pservice_id' ), + object_id = handle_dict.get('object_id'), + uri = handle_dict.get('uri'), + ) return result diff --git a/sugar/activity/activityservice.py b/sugar/activity/activityservice.py index bff42fa..e5b8956 100644 --- a/sugar/activity/activityservice.py +++ b/sugar/activity/activityservice.py @@ -29,6 +29,21 @@ class ActivityService(dbus.service.Object): tightly control what stuff passes through the dbus python bindings.""" def __init__(self, activity): + """Initialise the service for the given activity + + activity -- sugar.activity.activity.Activity instance, + must have already bound it's window (i.e. it must + have already initialised to the point of having + the X window available). + + Creates dbus services that use the xid of the activity's + root window as discriminants among all active services + of this type. That is, the services are all available + as names/paths derived from the xid for the window. + + The various methods exposed on dbus are just forwarded + to the client Activity object's equally-named methods. + """ xid = activity.window.xid service_name = _ACTIVITY_SERVICE_NAME + '%d' % xid object_path = _ACTIVITY_SERVICE_PATH + "/%s" % xid diff --git a/sugar/activity/bundle.py b/sugar/activity/bundle.py index 8cb1945..2ea5a54 100644 --- a/sugar/activity/bundle.py +++ b/sugar/activity/bundle.py @@ -1,3 +1,4 @@ +"""Metadata description of a given application/activity""" import logging import locale import os @@ -8,7 +9,17 @@ from sugar import env _PYTHON_FACTORY='sugar-activity-factory' class Bundle: - """Info about an activity bundle. Wraps the activity.info file.""" + """Metadata description of a given application/activity + + The metadata is normally read from an activity.info file, + which is an INI-style configuration file read using the + standard Python ConfigParser module. + + The format reference for the Bundle definition files is + available for further reference: + + http://wiki.laptop.org/go/Activity_bundles + """ def __init__(self, path): self._name = None self._icon = None diff --git a/sugar/activity/bundleregistry.py b/sugar/activity/bundleregistry.py index 7b12492..58b1700 100644 --- a/sugar/activity/bundleregistry.py +++ b/sugar/activity/bundleregistry.py @@ -15,8 +15,25 @@ def _get_data_dirs(): return [ '/usr/local/share/', '/usr/share/' ] class _ServiceManager(object): + """Internal class responsible for creating dbus service files + + DBUS services are defined in files which bind a service name + to the name of an executable which provides the service name. + + In Sugar, the service files are automatically generated from + the activity registry (by this class). When an activity's + dbus launch service is requested, dbus will launch the + specified executable in order to allow it to provide the + requested activity-launching service. + + In the case of activities which provide a "class", instead of + an "exec" attribute in their activity.info, the + sugar-activity-factory script is used with an appropriate + argument to service that bundle. + """ + SERVICE_DIRECTORY = '~/.local/share/dbus-1/services' def __init__(self): - service_dir = os.path.expanduser('~/.local/share/dbus-1/services') + service_dir = os.path.expanduser(self.SERVICE_DIRECTORY) if not os.path.isdir(service_dir): os.makedirs(service_dir) diff --git a/sugar/date.py b/sugar/date.py index 331124b..2894a87 100644 --- a/sugar/date.py +++ b/sugar/date.py @@ -1,10 +1,25 @@ +"""Simple date-representation model""" import datetime class Date(object): + """Date-object storing a simple time.time() float + + XXX not sure about the rationale for this class, + possibly it makes transfer over dbus easier? + """ def __init__(self, timestamp): + """Initialise via a timestamp (floating point value)""" self._timestamp = timestamp def __str__(self): + """Produce a formatted date representation + + Eventually this should produce a localised version + of the date. At the moment it always produces English + dates in long form with Today and Yesterday + special-cased and dates from this year not presenting + the year in the date. + """ date = datetime.date.fromtimestamp(self._timestamp) today = datetime.date.today() diff --git a/sugar/env.py b/sugar/env.py index 00539b5..f25f460 100644 --- a/sugar/env.py +++ b/sugar/env.py @@ -1,3 +1,4 @@ +"""Calculates file-paths for the Sugar working environment""" # Copyright (C) 2006, Red Hat, Inc. # # This library is free software; you can redistribute it and/or diff --git a/sugar/logger.py b/sugar/logger.py index bd01f2e..87a9c96 100644 --- a/sugar/logger.py +++ b/sugar/logger.py @@ -1,3 +1,4 @@ +"""Logging module configuration for Sugar""" # Copyright (C) 2006, Red Hat, Inc. # # This library is free software; you can redistribute it and/or diff --git a/sugar/profile.py b/sugar/profile.py index 4df0f52..840e101 100644 --- a/sugar/profile.py +++ b/sugar/profile.py @@ -1,3 +1,4 @@ +"""User settings/configuration loading""" # Copyright (C) 2006, Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify @@ -23,6 +24,30 @@ from sugar import util from sugar.graphics.xocolor import XoColor class _Profile(object): + """Local user's current options/profile information + + User settings are stored in an INI-style configuration + file. This object uses the ConfigParser module to load + the settings. At the moment the only storage mechanism + is in the set_server_registered method, which loads the + file directly from disk, then dumps it back out again + immediately, rather than using the class. + + The profile is also responsible for loading the user's + public and private ssh keys from disk. + + Attributes: + + name -- child's name + color -- XoColor for the child's icon + server -- school server with which the child is + associated + server_registered -- whether the child has registered + with the school server or not + + pubkey -- public ssh key + privkey_hash -- SHA has of the child's public key + """ def __init__(self): self.name = None self.color = None diff --git a/sugar/util.py b/sugar/util.py index 8ad840d..58fcc7b 100644 --- a/sugar/util.py +++ b/sugar/util.py @@ -1,3 +1,4 @@ +"""Various utility functions""" # Copyright (C) 2006, Red Hat, Inc. # # This library is free software; you can redistribute it and/or @@ -38,6 +39,19 @@ def _sha_data(data): return sha_hash.digest() def unique_id(data = ''): + """Generate a likely-unique ID for whatever purpose + + data -- suffix appended to working data before hashing + + Returns a 40-character string with hexidecimal digits + representing an SHA hash of the time, a random digit + within a constrained range and the data passed. + + Note: these are *not* crypotographically secure or + globally unique identifiers. While they are likely + to be unique-enough, no attempt is made to make + perfectly unique values. + """ data_string = "%s%s%s" % (time.time(), random.randint(10000, 100000), data) return printable_hash(_sha_data(data_string)) @@ -49,7 +63,7 @@ def is_hex(s): def validate_activity_id(actid): """Validate an activity ID.""" - if not isinstance(actid, str) and not isinstance(actid, unicode): + if not isinstance(actid, (str,unicode)): return False if len(actid) != ACTIVITY_ID_LEN: return False @@ -62,6 +76,23 @@ class _ServiceParser(ConfigParser): return option def write_service(name, bin, path): + """Write a D-BUS service definition file + + These are written by the bundleregistry when + a new activity is registered. They bind a + D-BUS bus-name with an executable which is + to provide the named service. + + name -- D-BUS service name, must be a valid + filename/D-BUS name + bin -- executable providing named service + path -- directory into which to write the + name.service file + + The service files themselves are written using + the _ServiceParser class, which is a subclass + of the standard ConfigParser class. + """ service_cp = _ServiceParser() section = 'D-BUS Service' service_cp.add_section(section) |