Collaboration Tutorial

From OLPC
Revision as of 14:25, 13 June 2008 by Morgs (talk | contribs)
Jump to navigation Jump to search
  This page is monitored by the OLPC team.

This tutorial focuses on activity sharing. Read Activity sharing for an overview of the concepts. See also Presence Service and Tubes.

See Collaboration Central for all things relating to activity collaboration.

Introduction

Collaboration is implemented with Telepathy, a D-Bus framework for Instant Messaging protocols. The Presence Service provides functionality to Sugar to track shared Activities and Buddies (and show them on the Neighborhood View), and talks to Telepathy connection managers for XMPP via a Jabber server (telepathy-gabble), and Link Local XMPP (telepathy-salut).

XMPP is used to provide presence information about Buddies (other XOs you can collaborate with). The mesh view is like an instant messenger buddy list, represented in a more graphical way.

Not all the Buddies you see are on the same network as you. In particular, any of them could be behind a NAT firewall, and so you cannot simply open a TCP/IP socket to them to exchange data. Furthermore, while it is not yet implemented, Bitfrost's P_NET protection will restrict activities' use of networking, but not apply to collaboration via Telepathy.[1]

XMPP via the Jabber server on the school server allows you to exchange data with all the Buddies you can see, whether or not they are directly reachable from your XO.

Shared Activities

A shared activity is implemented using an XMPP MUC (multi user chat room). Buddies that are in the room, are in the shared activity.

Presence Service provides each shared activity with a Telepathy Text Channel by default. This text channel is a connection to the XMPP chat room, but is not useful for data sharing directly. You can see an example of this Text Channel in direct use in the Chat activity. In future, Sugar will provide "overlay chat" to activities, allowing you to do text chat with the participants of a shared activity.

On top of this Text Channel, PS provides a Tubes channel to exchange data with the other participants via Tubes.

Tubes

There are two types of Activity sharing#Tubes at present: D-Bus Tubes and Stream Tubes.

D-Bus Tubes provide D-Bus functionality to signal and call methods on everyone in the room.

Stream Tubes wrap TCP/IP sockets and are more suited to streaming data between two participants.

D-Bus Tubes

See the D-Bus tutorial and the dbus-python tutorial.

D-Bus provides signals and methods:

  • Signals are multicast - they are sent to all participants in the shared activity (including the sender). They send data and have no return value.
  • Method calls are called on a single participant, and they do have a return value.

It is important to design your Activity's D-Bus Tubes API well, to support the functionality that your collaboration will need.

More to come here...

Bus names, handles, Buddy objects

Signals and Methods use bus names to identify senders and recipients. Telepathy uses handles to identify participants. Sugar uses Buddy objects.

The TubeConnection object can convert between bus names and handles - see tubeconn.py for details.

More to come here...

Stream Tubes

See the Read activity's code for an example. It's in git://dev.laptop.org/projects/read-activity and is built by default in sugar-jhbuild.

More to come here...

Example Activity: HelloMesh

HelloMesh is a demo activity with sample code using a D-Bus Tube. The code is in git://dev.laptop.org/projects/hellomesh and can be built in sugar-jhbuild with ./sugar-jhbuild buildone hellomesh

First, let's look at HelloMesh's D-Bus Tubes API. There are two signals and one method:

   @signal(dbus_interface=IFACE, signature=)
   def Hello(self):
       """Say Hello to whoever else is in the tube."""
       self._logger.debug('I said Hello.')
   @method(dbus_interface=IFACE, in_signature='s', out_signature=)
   def World(self, text):
       """To be called on the incoming XO after they Hello."""
       if not self.text:
           self._logger.debug('Somebody called World and sent me %s',
                              text)
           self._alert('World', 'Received %s' % text)
           self.text = text
           self.text_received_cb(text)
           # now I can World others
           self.add_hello_handler()
       else:
           self._logger.debug("I've already been welcomed, doing nothing")
   @signal(dbus_interface=IFACE, signature='s')
   def SendText(self, text):
       """Send some text to all participants."""
       self.text = text
       self._logger.debug('Sent text: %s', text)
       self._alert('SendText', 'Sent %s' % text)

When a new participant joins the shared activity, it sends the Hello signal. This goes to all existing participants. The Hello signal has an empty signature, since no actual parameters are being sent.

When an (existing) participant receives the Hello signal, it calls the World method of the signal's sender. The World method takes a string parameter (text) represented by

in_signature='s'

It doesn't return anything, hence

out_signature=''

Calling World when Hello is received is done in hello_cb:

   def hello_cb(self, sender=None):
       """Somebody Helloed me. World them."""
       if sender == self.tube.get_unique_name():
           # sender is my bus name, so ignore my own signal
           return
       self._logger.debug('Newcomer %s has joined', sender)
       self._logger.debug('Welcoming newcomer and sending them the game state')
       self.tube.get_object(sender, PATH).World(self.text,
                                                dbus_interface=IFACE)

Note that signals go to all participants, including yourself - the above code shows how to ignore your own signals.

sender is a bus (as in D-Bus) name, like :2.OWI0MTk2MzNAdmFpbwAA. Signals and Methods use these to identify senders and recipients.

In order to receive the Hello signal, we need to add a signal receiver:

   def add_hello_handler(self):
       self._logger.debug('Adding hello handler.')
       self.tube.add_signal_receiver(self.hello_cb, 'Hello', IFACE,
           path=PATH, sender_keyword='sender')
       self.tube.add_signal_receiver(self.sendtext_cb, 'SendText', IFACE,
           path=PATH, sender_keyword='sender')

You can see this called in the World method above, because in this example once we have had our World method called by an existing participant, we can then respond to somebody else's Hello signal.

This also adds a signal receiver for the SendText signal, defined above, which sends the text from the user interface.