Changes

Jump to navigation Jump to search
deletions and
Line 1: Line 1: −
<noinclude>{{Developers}}{{TOCright}}</noinclude>
+
<noinclude>{{Developers}}</noinclude>__TOC__
    
Sugar activities are usually written in Python using the [http://api.sugarlabs.org/ Python Activity API]. This page documents the underlying mechanism that all activities need to conform to. Activities can be written in any language, as long as it can connect to D-Bus and provide an X11 interface. The discussion below tries to be language-agnostic.
 
Sugar activities are usually written in Python using the [http://api.sugarlabs.org/ Python Activity API]. This page documents the underlying mechanism that all activities need to conform to. Activities can be written in any language, as long as it can connect to D-Bus and provide an X11 interface. The discussion below tries to be language-agnostic.
Line 22: Line 22:  
# It creates an X11 window with special properties so the Sugar shell can associate an activity with its window.
 
# It creates an X11 window with special properties so the Sugar shell can associate an activity with its window.
 
# If an object id was passed on the command line, the activity loads that object from the Datastore. Otherwise, it creates a new Datastore object.
 
# If an object id was passed on the command line, the activity loads that object from the Datastore. Otherwise, it creates a new Datastore object.
# The activity asks the Presence Service to find out if it is shared. If so, it joins the shared activity.
+
# It needs to set up collaboration by connecting to the shared activity instance (if any).
    
'''Operation'''
 
'''Operation'''
# The activity continuously handles X11 user input, as well as D-Bus messages from the Sugar shell, or signals from other sources like the Presence Service.
+
# The activity continuously handles X11 user input, as well as D-Bus messages from the Sugar shell, or signals from other sources like Telepathy (the collaboration framework).
 
# Whenever the state of the activity was altered significantly, it should update its Datastore object to prevent data loss on an unexpected shutdown.
 
# Whenever the state of the activity was altered significantly, it should update its Datastore object to prevent data loss on an unexpected shutdown.
# If the user indicates wanting to share the activity, it has to announce this to the Presence Service.
+
# If the user indicates wanting to share the activity, it needs to create a shared activity instance.
 
# If the view-source key is pressed, some meta-action about the activity should be invoked, like showing its source code.
 
# If the view-source key is pressed, some meta-action about the activity should be invoked, like showing its source code.
   Line 185: Line 185:  
Activities may query the datastore:
 
Activities may query the datastore:
   −
  (results,count) = datastore.find(query)
+
  (results,count) = datastore.find(query, properties)
   −
It returns the results as array of properties and a count of matching items (the array may have fewer items if the query was limited). In addition to the usual metadata items, the properties will include the object id at key 'uid', the mountpoint of the item at key 'mountpoint', and possibly a 'filename' if requested.
+
It returns the matching object's metadata as array of dictionaries, and a count of matching items (the array may have fewer items if the query was limited). In addition to the usual metadata items, each dictionary will include the object id at key 'uid', <strike>the mountpoint of the item at key 'mountpoint',</strike> and possibly a 'filename' if requested.
   −
The query can be a:
+
The '''properties''' argument is an array of strings. It defines which keys to include in the results. You should only specify the keys you actually need. If you pass an empty array here, all the metadata would be returned, which for a Journal with hundreds of entries and previews could amount to multiple megabytes of data.
   −
: <b>string</b>: fulltext search
+
The '''query''' argument is a dictionary. If empty, all the datastore's entries will be returned, but again, you should avoid that. The key-value pairs specify the value (or array of values, or dictionary specifying range) for a specific property, e.g.:
:: the given string is searched in all text properties
+
: 'title' = 'First Project' <i>(but see note below)</i>
: <b>dictionary</b>: structured query
+
: 'mime_type' = ['image/png', 'image/jpeg']
:: the key-value pairs in the dictionary specify the value (or array of values, or dictionary specifying range) for a specific property, e.g.:
+
: 'mtime' = {'start' = '2007-07-01T00:00:00', 'end' = '2007-08-01T00:00:00'}
::: 'title' = 'First Project' <i>(but see note below)</i>
+
A few specific keys adjust the query:
::: 'mime_type' = ['image/png', 'image/jpeg']
+
: 'query': fulltext search term (supports some [http://xapian.org/docs/queryparser.html special operators])
::: 'mtime' = {'start' = '2007-07-01T00:00:00', 'end' = '2007-08-01T00:00:00'}
+
: 'order_by': sort key (or array of keys) to order results by, to reverse order use '-key' <i>(but see note below)</i>
:: also, there are a few specific keys to adjust the query:
+
: 'limit', 'offset': return only limit results starting at offset
::: 'query': fulltext search term
+
: <strike>'mountpoints': array of [[#Mount Points|mountpoint ids]] to search (or all if not specified) </strike>
::: 'order_by': key (or array of keys) to order results by, to reverse order use '-key' <i>(but see note below)</i>
+
: 'include_files': if true, generate files as if get_filename() had been called for each item. In results, a property 'filename' will be added.
::: 'limit', 'offset': return only limit results starting at offset
  −
::: 'mountpoints': array of mountpoint ids to search (or all if not specified)
  −
::: 'include_files': if true, generate files as if get_filename() had been called for each item. In results, a property 'filename' will be added.
     −
<i>NOTE: Since Sugar 0.84 only very few keys are supported ('uid', 'activity', 'activity_id', 'mime_type',  and 'keep', see [http://git.sugarlabs.org/projects/sugar-datastore/repos/mainline/blobs/master/src/carquinyol/indexstore.py#line32 here]). Other keys are ignored so you may get back more results than expected. Results are ordered by timestamp by default. Sort keys are limited, too (none in 0.84, in 0.86 'timestamp' and 'title', see [http://git.sugarlabs.org/projects/sugar-datastore/repos/mainline/blobs/master/src/carquinyol/indexstore.py#line266 here]).</i>
+
<i>NOTE: In Sugar 0.82 you cannot search for 'uid'. Since Sugar 0.84 only very few keys are supported in the query ('uid', 'activity', 'activity_id', 'mime_type',  and 'keep', see [http://git.sugarlabs.org/projects/sugar-datastore/repos/mainline/blobs/master/src/carquinyol/indexstore.py#line32 here]). Other keys are ignored so you may get back more results than expected. Results are ordered by timestamp by default. Sort keys are limited, too (none in 0.84, in 0.86 'timestamp' and 'title', see [http://git.sugarlabs.org/projects/sugar-datastore/repos/mainline/blobs/master/src/carquinyol/indexstore.py#line266 here]).</i>
    
You can also retrieve an array of unique values for a field:
 
You can also retrieve an array of unique values for a field:
Line 211: Line 208:  
  values = datastore.get_uniquevaluesfor(property, query)
 
  values = datastore.get_uniquevaluesfor(property, query)
   −
<i>Note that currently (2007-07-25) the query is ignored in this call, it looks for all values in all entries.</i>
+
''NOTE: currently (2007-07-25) the query is ignored in this call, it looks for all values in all entries. And in more recent Sugar versions, the only property allowed is 'activity'.''
 +
 
 +
==Watching==
 +
 
 +
To keep cached information in your activity up-to-date, you can subscribe to these signals:
 +
 
 +
datastore.Created(object_id)
 +
datastore.Updated(object_id)
 +
datastore.Deleted(object_id)
 +
 
 +
E.g. if you display a document title in your activity, and the user changes that entry in the Journal while your activity is running, you can use the Updated signal to reflect that change. Otherwise, when you save the document, it will overwrite the title set by the user.
 +
 
 +
Similarly, if you display a list of Journal entries in your activity, it would have to be updated in response to the Created and Deleted signals.
    
==Progress Display==
 
==Progress Display==
Line 237: Line 246:  
===Choosing Objects===
 
===Choosing Objects===
 
Call this method to bring up the Chooser dialog (which looks like a small journal overlayed on your activity):
 
Call this method to bring up the Chooser dialog (which looks like a small journal overlayed on your activity):
  chooser_id = org.laptop.Journal.ChooseObject(xid, what_filter)
+
 
The xid should be your activity's X window handle, or <tt>0</tt>. The filter says what type of Journal entries is preselected in the drop-down menu (''this parameter was added in Sugar 0.83''). It's a string containing either a bundle id (e.g., <tt>'my.organization.MyActivity'</tt>), or one of the generic data types (<tt>'Text', 'Image', 'Video', 'Audio', 'Link'</tt> [http://git.sugarlabs.org/projects/sugar-base/repos/mainline/blobs/master/src/sugar/mime.py#line32 definition]), or an empty string for no filter. The call returns immediately with a string chooser_id. You need to watch these signals which get emitted when an item is chosen or the dialog is canceled:
+
  chooser_id = org.laptop.Journal.ChooseObject(xid:s, what_filter:s)                     # in Sugar 0.82 this had only one argument
  ObjectChooserResponse(chooser_id, object_id)
+
 
  ObjectChooserCancelled(chooser_id)
+
The xid should be your activity's X window handle, or <tt>0</tt>. The filter says what type of Journal entries is preselected in the drop-down menu (''this parameter was added in Sugar 0.83''). It's a string containing either a bundle id (e.g., <tt>'my.organization.MyActivity'</tt>), or one of the generic data types (<tt>'Text', 'Image', 'Video', 'Audio', 'Link'</tt> [http://git.sugarlabs.org/projects/sugar-base/repos/mainline/blobs/master/src/sugar/mime.py#line32 definition]), or an empty string for no filter. The call returns immediately with a string chooser_id. You need to watch the following signals which get emitted when an item is chosen or the dialog is cancelled:
The object_id received in an ObjectChooserResponse signal then can be used to open the corresponding [[#Keeping and Resuming|datastore object]].
+
 
 +
  ObjectChooserResponse(chooser_id:s, object_id_or_path:s)
 +
  ObjectChooserCancelled(chooser_id:s)
 +
 
 +
The chooser_id can be tested to see if this signal was in response to our own request. This is important if the user opens two file choosers at the same time (possibly in two different activities). If the user closes the chooser without selecting anything, the ObjectChooserCancelled signal is sent. Otherwise, the ObjectChooserResponse signal will be sent, and its object_id_or_path string argument is either an object id or a file path. To distinguish between the two cases, check if the first character is a slash (<tt>'/'</tt>). If it indeed begins with a slash, then this is an absolute path to a file, which can be opened directly. Otherwise, if the first character is not a slash, then object_id_or_path is an object id, which can be used to open the corresponding [[#Keeping and Resuming|datastore object]].
    
===Focusing Objects===
 
===Focusing Objects===
Line 248: Line 261:  
  org.laptop.Journal.ShowObject(object_id)
 
  org.laptop.Journal.ShowObject(object_id)
   −
Switches to the Journal activity and shows the selected object.
+
Switches to the Journal activity and shows the "detail view" of the selected object.
 +
 
 +
This can be used to, e.g., open a URL (create a URL object in the journal then call this) or to view source code (store the source code as text file, then call ShowObject() on it).
   −
This can be used to, e.g., open a URL (create a URL object in the journal then call this) or to view source code (store the source code as text file in the journal then call ShowObject() on it).
+
Note: since Sugar 0.84, you also can give the full path to an existing file instead of an object id. If the user resumes that file, it will be copied to the Journal first and then opened using the chosen activity. <i>This seems to be of limited usefulness and is documented here just for completeness</i>
    
<s>To focus on multiple objects: <code>org.laptop.Journal.FocusSearch(query)</code></s> ''Removed in Sugar 0.83''
 
<s>To focus on multiple objects: <code>org.laptop.Journal.FocusSearch(query)</code></s> ''Removed in Sugar 0.83''
    
==Mount Points==
 
==Mount Points==
 +
<strike>
 
Devices are represented as mount points in the datastore. If no mountpoint is explicitly specified, the main datastore (Journal) is used.
 
Devices are represented as mount points in the datastore. If no mountpoint is explicitly specified, the main datastore (Journal) is used.
   Line 261: Line 277:  
Returns an array of mount point descriptors where each descriptor is a dictionary containing at least the following keys:
 
Returns an array of mount point descriptors where each descriptor is a dictionary containing at least the following keys:
 
:'id': the id used to refer explicitly to the mount point
 
:'id': the id used to refer explicitly to the mount point
:'title': Human readable identifier for the mountpoint
+
:'title': Human readable identifier for the mountpoint ''(in Sugar 0.82, just the uri)''
 
:'uri': The uri which triggered the mount
 
:'uri': The uri which triggered the mount
   Line 267: Line 283:     
Large files to be stored on an external device should be placed at the uri of the mount point (see [[#External Media|external media]]).
 
Large files to be stored on an external device should be placed at the uri of the mount point (see [[#External Media|external media]]).
 +
</strike>
 +
 +
'''Note:''' ''This only works as described in Sugar up to 0.82. The functions do exist in later Sugar versions, but do not return useful data. Use the [[#External Media|external media API]] instead.''
    
=Security=
 
=Security=
Line 293: Line 312:  
;$SUGAR_ACTIVITY_ROOT/data/: This directory is used similar to a traditional home directory, for persistent activity data such as configuration files. Make sure files in there are group readable and writable (see [[#Users and Groups|users and groups]]). The directory itself is group-writable. Files stored here will survive reboots and OS upgrades.
 
;$SUGAR_ACTIVITY_ROOT/data/: This directory is used similar to a traditional home directory, for persistent activity data such as configuration files. Make sure files in there are group readable and writable (see [[#Users and Groups|users and groups]]). The directory itself is group-writable. Files stored here will survive reboots and OS upgrades.
   −
;$SUGAR_ACTIVITY_ROOT/tmp/: This directory is used similar to a /tmp directory, being backed by RAM. It may be as small as 1 MB. This directory is deleted when the activity exits (specifically,  as soon as all children of the activity's first process die). This directory is ''only'' accessible to the activity and its children; not even to Sugar.
+
;$SUGAR_ACTIVITY_ROOT/tmp/: This directory is used similar to a /tmp directory, being backed by RAM if [[olpc:Rainbow|Rainbow]] is present. It may be as small as 1 MB. With [[olpc:Rainbow|Rainbow]], this directory is deleted when the activity exits (specifically,  as soon as all children of the activity's first process die). This directory is ''only'' accessible to the activity and its children; not even to Sugar.
   −
;$SUGAR_ACTIVITY_ROOT/instance/: This directory is used similar to a /var/tmp directory, being backed by flash rather than by RAM. It is unique per instance. It is used for transfer to and from the datastore (see [[#Keeping and Resuming|keeping and resuming]]). This directory is deleted when the activity exits (specifically,  as soon as all children of the activity's first process die)
+
;$SUGAR_ACTIVITY_ROOT/instance/: This directory is used similar to a /var/tmp directory, being backed by flash rather than by RAM. It is unique per instance. It is used for transfer to and from the datastore (see [[#Keeping and Resuming|keeping and resuming]]). With [[olpc:Rainbow|Rainbow]], this directory is deleted when the activity exits (specifically,  as soon as all children of the activity's first process die).
    
As of version 8.2, all the activity root directories are created in /home/olpc/isolation and can be examined there with the Terminal activity. However, activities MUST use the $SUGAR_ACTIVITY_ROOT variable because the isolation directory layout is expected to change.
 
As of version 8.2, all the activity root directories are created in /home/olpc/isolation and can be examined there with the Terminal activity. However, activities MUST use the $SUGAR_ACTIVITY_ROOT variable because the isolation directory layout is expected to change.
Line 305: Line 324:  
=== External Media ===
 
=== External Media ===
   −
External media (USB drives, SD cards) are auto-mounted by the Journal and appear in /media/*. No access restrictions are applied currently (up to release 8.2). If activities use these external media directly (rather than through the Journal, see [[#Mount Points|mount points]]), they need to take care of ensuring data integrity since the user may (and will) remove the medium at any time.
+
External media (USB drives, SD cards) are auto-mounted by the Journal. No access restrictions are applied currently. If activities use these external volumes directly, they need to take care of ensuring data integrity since the user may (and will) remove the medium at any time.
 +
 
 +
Depending on the Linux distro version, the volumes appear as /mnt/*, /media/*, /run/media/$USER/*, or in other places. Sugar up to version 0.82 provided an API to access their [[#Mount Points|mount points]]. Nowadays you should use general Linux API to find the volumes. The recommended way is using the [http://developer.gnome.org/gio/stable/GVolumeMonitor.html GNOME Volume Monitor] with a fallback to [http://www.freedesktop.org/wiki/Software/hal HAL] (there is an example in the [http://git.sugarlabs.org/backup/mainline/blobs/master/backup.py#line671 Backup activity]).
    
== Signing ==
 
== Signing ==
Line 323: Line 344:  
Collaboration plays a large role in Sugar. Still, the presence and sharing APIs are still somewhat rough. There are attempts to explain sharing, see [[olpc:Activity sharing|Activity sharing]]. The following are the bare essentials.
 
Collaboration plays a large role in Sugar. Still, the presence and sharing APIs are still somewhat rough. There are attempts to explain sharing, see [[olpc:Activity sharing|Activity sharing]]. The following are the bare essentials.
    +
''Note: The Presence Service has been deprecated. Activities need to talk to [[Telepathy]] directly instead. See [[#Collaboration|below]] for details''
 +
 +
<strike>
 
==General==
 
==General==
   Line 377: Line 401:  
  BuddyJoined (o: buddy)
 
  BuddyJoined (o: buddy)
 
  BuddyLeft (o: buddy)
 
  BuddyLeft (o: buddy)
 +
 +
</strike>
    
==Tubes==
 
==Tubes==
Line 428: Line 454:     
===D-Bus Tubes===
 
===D-Bus Tubes===
''to be continued. In the mean time, see [[olpc:Presence Service D-Bus API]] and [http://telepathy.freedesktop.org/spec-0.16.html#org.freedesktop.Telepathy.Channel.Type.Tubes Tubes]''
+
''see [[olpc:Presence Service D-Bus API]] and [http://telepathy.freedesktop.org/spec-0.16.html#org.freedesktop.Telepathy.Channel.Type.Tubes Tubes]''
 +
 
 +
= Collaboration =
 +
 
 +
Sharing and collaboration are core principles in Sugar. They are implemented using [[Telepathy]].
 +
 
 +
'''Note:''' ''The [[#Presence|Presence Service]] (which was a wrapper around Telepathy in earlier Sugar releases) has been deprecated. Activities need to use Telepathy directly.''
 +
 
 +
== Telepathy ==
 +
 
 +
For details, please refer to the [http://telepathy.freedesktop.org/spec/ Telepathy Specification] and the [http://telepathy.freedesktop.org/doc/book/ Telepathy Manual]. The code below is pseudo code, you need to adapt it for your specific programming language.
 +
 
 +
The entry point to Telepathy is the AccountManager:
 +
 
 +
Service:    org.freedesktop.Telepathy.AccountManager
 +
Interface:  org.freedesktop.Telepathy.AccountManager
 +
Object Path: /org/freedesktop/Telepathy/AccountManager
 +
 
 +
It has a list of valid Accounts. Each account has a Connection property, which is valid if its D-Bus path is not '/', and connected if the account's ConnectionStatus property is 0 (1 means connecting, 2 disconnected).
 +
 
 +
accounts = accountManager.Get('ValidAccounts')
 +
for account in accounts:
 +
    connection = account.Get('Connection')
 +
    if connection.path != '/' && account.Get('ConnectionStatus') == 0:
 +
        connections.add(connection)
 +
 
 +
This gathers all the usable connections, there may be more than one. Since connections can become available later, you need to either track changes using D-Bus signals, or do the above every time a new connection is needed.
 +
 
 +
== Startup ==
 +
 
 +
On startup, an activity needs to check all the currently available connections if there is already a "room" for it. The GetActivity method checks for the room given an activity_id:
 +
 
 +
for connection in connections:
 +
    room_handle = connection.GetActivity(activityId)              # may fail
 +
 
 +
It is provided by the non-standard ActivityProperties interface:
 +
 
 +
INTERFACE org.laptop.Telepathy.ActivityProperties
 +
METHOD GetActivity(activity_id:s) => (room:u)
 +
METHOD GetProperties(room:u) => (properties:a{sv})
 +
METHOD SetProperties(room:u, properties:a{sv}) => ()
 +
SIGNAL ActivityPropertiesChanged(room:u, properties:a{sv})
 +
 
 +
If a room is found, the activity needs to join it immediately (see [[#Joining|below]]). Otherwise, a normal "unshared" activity startup should commence.
 +
 
 +
== Joining ==
 +
 
 +
If the activity finds itself to be shared already on startup, or the user chooses to enable sharing later, it needs to "join" the room used for communication:
 +
 
 +
* create text channel
 +
* create tubes channel
 +
* add self to group
 +
 
 +
''to be documented''
 +
 
 +
== Leaving ==
 +
 
 +
''to be documented''
 +
 
 +
== Inviting ==
 +
 
 +
''to be documented''
 +
 
 +
== Communicating ==
 +
 
 +
This depends on the needs of the activity, but in general it would
 +
 
 +
* create a tube in tubes channel
 +
* use the tube for communication
 +
 
 +
''to be documented''
    
[[Category:API]]
 
[[Category:API]]
 
[[Category:Collaboration]]
 
[[Category:Collaboration]]

Navigation menu