[Commits] groupthink branch master updated.

bens bens at bemasc.localdomain
Mon Jan 12 00:07:20 EST 2009


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "/home/bemasc/public_git/groupthink".

The branch, master has been updated
       via  09689e2370808fb21cbe228165100df761d0e42e (commit)
      from  bb633dac532aa6908ac5d8eaafcbc72b04dab1cc (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

 __init__.py                         |    1 +
 groupthink.py => groupthink_base.py |  319 ++++++++++++++++++++++++++---------
 gtk.py                              |   26 +++
 sugar.py                            |  200 ++++++++++++++++++++++
 4 files changed, 464 insertions(+), 82 deletions(-)
 create mode 100644 __init__.py
 rename groupthink.py => groupthink_base.py (81%)
 create mode 100644 gtk.py
 create mode 100644 sugar.py

- Log -----------------------------------------------------------------
commit 09689e2370808fb21cbe228165100df761d0e42e
Author: bens <bens at bemasc.localdomain>
Date:   Sun Jan 11 23:27:05 2009 -0500

    Dont lose files...

diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..628753a
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1 @@
+from groupthink_base import *
diff --git a/groupthink.py b/groupthink_base.py
similarity index 81%
rename from groupthink.py
rename to groupthink_base.py
index 3f707ed..a2ab685 100644
--- a/groupthink.py
+++ b/groupthink_base.py
@@ -24,7 +24,7 @@ import logging
 import threading
 import thread
 import random
-from dobject_helpers import *
+from listset import ListSet
 
 """
 DObject is a library of components useful for constructing distributed
@@ -40,6 +40,38 @@ def PassFunction(*args):
 def ReturnFunction(x):
     return x
 
+class Group:
+    """A Group is a simple tool for organizing DObjects.  Once it is set up
+    with a tubebox, the user may simply add objects to it, e.g.
+    
+    self.group = Group(tb)
+    ...
+    self.group['mydict1'] = HighScore('No one', 0)
+    
+    and the group will take care of assigning a handler to the object with
+    the specified name.
+    For a Group g, g['a'] is equivalent in almost all ways to g.a, for
+    programmer convenience.
+    """
+    def __init__(self, tubebox):
+        self.tubebox = tubebox
+        self._d = dict()
+    
+    def __setitem__(self, name, dobj):
+        h = dobj.HANDLER_TYPE(name, self.tubebox)
+        dobj.set_handler(h)
+        self._d[name] = dobj
+    
+    __setattr__ = __setitem__
+    
+    def __getitem__(self, name):
+        return self._d[name]
+    
+    __getattr__ = __getitem__
+    
+    def __delattr(self, name):
+        raise #Deletion is not supported
+
 class TubeBox:
     """ A TubeBox is a box that either contains a Tube or does not.
     The purpose of a TubeBox is to solve this problem: Activities are not
@@ -175,37 +207,12 @@ class TimeHandler(dbus.gobject_service.ExportedGObject):
             self._know_offset = True
         self._offset_lock.release()
 
-
 class UnorderedHandler(dbus.gobject_service.ExportedGObject):
-    """ The most basic DObject is the Unordered Object (UO).  A UO has the
-    property that any changes to its state can be encapsulated as messages, and
-    these messages have no intrinsic ordering.  Different instances of the same
-    UO, after receiving the same messages in different orders, should reach the
-    same state.
-    
-    Any UO could be implemented as a set of all messages received so far, and
-    coherency could be maintained by sending all messages ever transmitted to
-    each new joining member.  However, many UOs will have the property that most
-    messages are obsolete, and need not be transmitted. Therefore, as an
-    optimization, UOs manage their own state structures for synchronizing state
-    with joining/merging users.
-    
-    Each UO should accept a UnorderedHandler as one of its constructor's arguments
-    Whenever an action is taken on the local UO (e.g. a method call that changes
-    the object's state), the UO must call handler.send() with an appropriately
-    encoded message.  Every UO must implement three methods:
-    
-    receive_message(msg):
-    This method accepts and processes a message sent via handler.send().
-    Because objects are sent over DBus, it is advisable to DBus-ify the message
-    before calling send, and de-DBus-ify it inside receive_message.
-    
-    get_history():
-    This method returns an encoded copy of all non-obsolete state, ready to be
-    sent over DBus.
-    
-    add_history(state):
-    This method accepts and processes the state object returned by get_history()
+    """The UnorderedHandler serves as the interface between a local UnorderedObject
+    (a pure python entity) and the d-bus/network system.  Each UnorderedObject
+    is associated with a single Handler, and vice-versa.  It is the Handler that
+    is actually exposed over D-Bus.  The purpose of this system is to minimize
+    the amount of networking code required for each additional UnorderedObject.
     """
     IFACE = "org.dobject.Unordered"
     BASEPATH = "/org/dobject/Unordered/"
@@ -314,10 +321,67 @@ class UnorderedHandler(dbus.gobject_service.ExportedGObject):
         with a different name every time."""
         return UnorderedHandler(self._myname + "/" + name, self._tube_box)
 
+class HandlerAcceptor:
+    HANDLER_TYPE = NotImplementedError
+    def set_handler(self, handler):
+        raise NotImplementedError
+
+class UnorderedObject(HandlerAcceptor):
+    """ The most basic DObject is the Unordered Object (UO).  A UO has the
+    property that any changes to its state can be encapsulated as messages, and
+    these messages have no intrinsic ordering.  Different instances of the same
+    UO, after receiving the same messages in different orders, should reach the
+    same state.
+    
+    Any UO could be implemented as a set of all messages received so far, and
+    coherency could be maintained by sending all messages ever transmitted to
+    each new joining member.  However, many UOs will have the property that most
+    messages are obsolete, and need not be transmitted. Therefore, as an
+    optimization, UOs manage their own state structures for synchronizing state
+    with joining/merging users.
+    
+    The following code is an abstract class for UnorderedObject, serving
+    primarily as documentation for the concept.
+    """
+    
+    HANDLER_TYPE = UnorderedHandler
+    handler = None
+
+    def set_handler(self, handler):
+        """Each UO must accept an UnorderedHandler via set_handler
+        Whenever an action is taken on the local UO (e.g. a method call that changes
+        the object's state), the UO must call handler.send() with an appropriately
+        encoded message.
+        
+        Subclasses may override this method if they wish to perform more actions
+        when a handler is set."""
+        if self.handler:
+            raise
+        else:
+            self.handler = handler
+            self.handler.register(self)
+            
+
+    def receive_message(self,msg):
+        """This method accepts and processes a message sent via handler.send().
+        Because objects are sent over DBus, it is advisable to DBus-ify the message
+        before calling send, and de-DBus-ify it inside receive_message."""
+        raise NotImplementedError
+    
+    def get_history(self):
+        """This method returns an encoded copy of all non-obsolete state, ready to be
+        sent over DBus."""
+        raise NotImplementedError
+    
+    def add_history(self, state):
+        """This method accepts and processes the state object returned by get_history()"""
+        raise NotImplementedError
+        
+
 def empty_translator(x, pack):
     return x
 
-class HighScore:
+class HighScore(UnorderedObject):
     """ A HighScore is the simplest nontrivial DObject.  A HighScore's state consists
     of a value and a score.  The user may suggest a new value and score.  If the new
     score is higher than the current score, then the value and score are updated.
@@ -334,7 +398,7 @@ class HighScore:
     random number to each message, and thereby reduces the probability of a tie
     by a factor of 2**52.
     """
-    def __init__(self, handler, initval, initscore, value_translator=empty_translator, score_translator=empty_translator, break_ties=False):
+    def __init__(self, initval, initscore, value_translator=empty_translator, score_translator=empty_translator, break_ties=False):
         self._logger = logging.getLogger('stopwatch.HighScore')
         self._lock = threading.Lock()
         self._value = initval
@@ -349,10 +413,8 @@ class HighScore:
         self._val_trans = value_translator
         self._score_trans = score_translator
         
-        self._handler = handler
-        self._handler.register(self)
-        
         self._listeners = []
+
     
     def _set_value_from_net(self, val, score, tiebreaker):
         self._logger.debug("set_value_from_net " + str(val) + " " + str(score))
@@ -375,8 +437,8 @@ class HighScore:
         score will be broadcast to all other participants.
         """
         self._logger.debug("set_value " + str(val) + " " + str(score))
-        if self._actually_set_value(val, score, None):
-            self._handler.send(self.get_history())
+        if self._actually_set_value(val, score, None) and self.handler:
+            self.handler.send(self.get_history())
             
     def _actually_set_value(self, value, score, tiebreaker):
         self._logger.debug("_actually_set_value " + str(value)+ " " + str(score))
@@ -455,6 +517,13 @@ def float_translator(f, pack):
     else:
         return float(f)
 
+def uint_translator(f, pack):
+    """This translator packs and unpacks 64-bit uints for dbus serialization"""
+    if pack:
+        return dbus.UInt64(f)
+    else:
+        return int(f)
+
 def string_translator(s, pack):
     """This translator packs and unpacks unicode strings for dbus serialization"""
     if pack:
@@ -462,36 +531,81 @@ def string_translator(s, pack):
     else:
         return str(s)
 
-class Latest:
+class Latest(HandlerAcceptor):
     """ Latest is a variation on HighScore, in which the score is the current
     timestamp.  Latest uses TimeHandler to provide a groupwide coherent clock.
     Because TimeHandler's guarantees about synchronization and resilience are
     weak, Latest is not as resilient to failures as a true DObject.
     
-    The creator must provide a UnorderedHandler and the initial value.  One may
+    The creator must provide  the initial value.  One may
     optionally indicate the initial time (as a float in epoch-time), a
     TimeHandler (otherwise a new one will be created), and a translator for
     serialization of the values.
+    
+    Note that if time_handler is not provided, the object will not be functional
+    until set_handler is called.
     """
-    def __init__(self, handler, initval, inittime=float('-inf'), time_handler=None, translator=empty_translator):
-        if time_handler is None:
-            self._time_handler = TimeHandler(handler.get_path(), handler.get_tube())
-        else:
-            self._time_handler = time_handler
+    def __init__(self, initval, inittime=float('-inf'), time_handler=None, translator=empty_translator):
+        self._time_handler = time_handler
         
         self._listeners = []
         self._lock = threading.Lock()
         
-        self._highscore = HighScore(handler, initval, inittime, translator, float_translator)
+        self._highscore = HighScore(initval, inittime, translator, float_translator)
         self._highscore.register_listener(self._highscore_cb)
     
+    def set_handler(self, handler):
+        if self.handler:
+            raise
+        else:
+            if self._time_handler is None:
+                self._time_handler = TimeHandler(handler.get_path(), handler.get_tube())
+            self._highscore.set_handler(handler)
+    
     def get_value(self):
         """ Returns the latest value """
         return self._highscore.get_value()
     
     def set_value(self, val):
         """ Suggest a new value """
-        self._highscore.set_value(val, self._time_handler.time())
+        if self._time_handler:
+            self._highscore.set_value(val, self._time_handler.time())
+        else:
+            raise #missing _time_handler
+    
+    def register_listener(self, L):
+        """ Register a listener L(value), to be called whenever another user
+        adds a new latest value."""
+        self._lock.acquire()
+        self._listeners.append(L)
+        self._lock.release()
+        L(self.get_value())
+    
+    def _highscore_cb(self, val, score):
+        for L in self._listeners:
+            L(val)
+
+class Recentest(HandlerAcceptor):
+    """ Recentest is like Latest, but without using a clock or TimeHandler.
+    As a result, it can only guarantee causality, not synchrony.
+    """
+    def __init__(self, initval, translator=empty_translator):
+        self._listeners = []
+        self._lock = threading.Lock()
+        
+        self._highscore = HighScore(initval, 0, translator, uint_translator, break_ties=True)
+        self._highscore.register_listener(self._highscore_cb)
+    
+    def set_handler(self, handler):
+        self._highscore.set_handler(handler)
+    
+    def get_value(self):
+        """ Returns the current value """
+        return self._highscore.get_value()
+    
+    def set_value(self, val):
+        """ Set a new value """
+        self._highscore.set_value(val, self._highscore.get_score() + 1)
     
     def register_listener(self, L):
         """ Register a listener L(value), to be called whenever another user
@@ -512,7 +626,7 @@ class AddOnlySet:
     is perfectly coherent, since the order in which elements are added is not
     important.
     """
-    def __init__(self, handler, initset = (), translator=empty_translator):
+    def __init__(self, initset = (), translator=empty_translator):
         self._logger = logging.getLogger('dobject.AddOnlySet')
         self._set = set(initset)
         
@@ -521,9 +635,6 @@ class AddOnlySet:
         self._trans = translator
         self._listeners = []  #This must be done before registering with the handler
 
-        self._handler = handler
-        self._handler.register(self)
-        
         self.__and__ = self._set.__and__
         self.__cmp__ = self._set.__cmp__
         self.__contains__ = self._set.__contains__
@@ -587,8 +698,8 @@ class AddOnlySet:
             self._send((y,))
     
     def _send(self, els):
-        if len(els) > 0:
-            self._handler.send(dbus.Array([self._trans(el, True) for el in els]))
+        if len(els) > 0 and self.handler is not None:
+            self.handler.send(dbus.Array([self._trans(el, True) for el in els]))
     
     def _net_update(self, y):
         s = set(y)
@@ -619,7 +730,7 @@ class AddOnlySet:
             L(s)
     
     def __repr__(self):
-        return 'AddOnlySet(' + repr(self._handler) + ', ' + repr(self._set) + ', ' + repr(self._trans) + ')'
+        return 'AddOnlySet(' + repr(self.handler) + ', ' + repr(self._set) + ', ' + repr(self._trans) + ')'
 
 class AddOnlySortedSet:
     """ AddOnlySortedSet is much like AddOnlySet, only backed by a ListSet, which
@@ -637,9 +748,6 @@ class AddOnlySortedSet:
 
         self._trans = translator
         self._listeners = []  #This must be done before registering with the handler
-
-        self._handler = handler
-        self._handler.register(self)
         
         self.__and__ = self._set.__and__
         self.__contains__ = self._set.__contains__
@@ -711,8 +819,8 @@ class AddOnlySortedSet:
             self._send((y,))
     
     def _send(self, els):
-        if len(els) > 0:
-            self._handler.send(dbus.Array([self._trans(el, True) for el in els]))
+        if len(els) > 0 and self.handler is not None:
+            self.handler.send(dbus.Array([self._trans(el, True) for el in els]))
     
     def _net_update(self, y):
         d = ListSet()
@@ -744,10 +852,9 @@ class AddOnlySortedSet:
             L(s)
     
     def __repr__(self):
-        return 'AddOnlySortedSet(' + repr(self._handler) + ', ' + repr(self._set) + ', ' + repr(self._trans) + ')'
-        
+        return 'AddOnlySortedSet(' + repr(self.handler) + ', ' + repr(self._set) + ', ' + repr(self._trans) + ')'
         
-def CausalHandler():
+class CausalHandler:
     """The CausalHandler is analogous to the UnorderedHandler, in that it
     presents an interface with which to build a wide variety of objects with
     distributed state.  The CausalHandler is different from the Unordered in two
@@ -777,7 +884,7 @@ def CausalHandler():
     _max64 = 2**64
 
     def __init__(self, name, tube_box):
-        self._unordered = UnorderedObject(name, tube_box)
+        self._unordered = UnorderedHandler(name, tube_box)
         self._counter = 0
         
         self._object = None
@@ -832,9 +939,53 @@ def CausalHandler():
         hist = dbus.Tuple((h, self.index_trans(self.get_index(), True)))
         return
 
-class CausalDict:
+class CausalObject:
+    """A CausalObject is almost precisely like an UnorderedObject, except
+    that whereas an UnorderedObject is completely specified by a set of messages,
+    a CausalObject is completely specified by an ordered list of messages,
+    sorted according to an opaque index associated with each message.
+    This index must be monotonically increasing in time for new messages as they
+    are created, but old messages may arrive long after they were created, and 
+    are then inserted into the middle of the timestream.
+    
+    The following code is an abstract class for CausalObject, serving
+    primarily as documentation for the concept.
+    """
+    
+    HANDLER_TYPE = CausalHandler
+    handler = None
+
+    def set_handler(self, handler):
+        """Each CO must accept an CausalHandler via set_handler.
+
+        Subclasses may override this method if they wish to perform more actions
+        when a handler is set."""
+        if self.handler:
+            raise
+        else:
+            self.handler = handler
+            self.handler.register(self)
+            
+    def receive_message(self, msg, index):
+        """This method accepts and processes a message sent via handler.send().
+        Because objects are sent over DBus, it is advisable to DBus-ify the message
+        before calling send, and de-DBus-ify it inside receive_message.
+        
+        The index argument is an opaque index used for determining the ordering."""
+        raise NotImplementedError
+    
+    def get_history(self):
+        """This method returns an encoded copy of all non-obsolete state, ready to be
+        sent over DBus."""
+        raise NotImplementedError
+    
+    def add_history(self, state):
+        """This method accepts and processes the state object returned by get_history()"""
+        raise NotImplementedError
+
+class CausalDict(CausalObject):
     """NOTE: CausalDict is UNTESTED.  Other things may be buggy, but CausalDict
-    PROBABLY DOES NOT WORK.
+    PROBABLY DOES NOT WORK. A CausalDict WILL NOT WORK UNTIL set_handler IS CALLED.
     
     CausalDict is a distributed version of a Dict (hash table).  All users keep
     a copy of the entire table, so this is not a "Distributed Hash Table"
@@ -864,12 +1015,8 @@ class CausalDict:
     DELETE = 1
     CLEAR = 2
 
-    def __init__(self, handler, initdict=(), key_translator=empty_translator, value_translator=empty_translator):
-        self._handler = handler
+    def __init__(self, initdict=(), key_translator=empty_translator, value_translator=empty_translator):
         self._dict = dict(initdict)
-        self._clear = self._handler.get_index() #this must happen before index_dict initialization, so that self._clear is less than any index in index_dict
-        self._index_dict = dict(((k, self._handler.get_index()) for k in initdict))
-        
         self._listeners = []
         
         self._key_trans = key_translator
@@ -901,26 +1048,34 @@ class CausalDict:
         #special setdefault
         #special update
         self.values = self._dict.values
-        
-        self._handler.register(self)
+
+    def set_handler(self, handler):
+        if self.handler is not None:
+            raise
+        else:    
+            self.handler = handler
+            self._clear = self.handler.get_index() #this must happen before index_dict initialization, so that self._clear is less than any index in index_dict
+            self._index_dict = dict(((k, self.handler.get_index()) for k in initdict))
+            
+            self.handler.register(self)
     
     def __delitem__(self, key):
         """Same as for dict"""
         del self._dict[key]
-        n = self._handler.send(((dbus.Int32(CausalDict.DELETE), self._key_trans(key, True))))
+        n = self.handler.send(((dbus.Int32(CausalDict.DELETE), self._key_trans(key, True))))
         self._index_dict[key] = n
     
     def __setitem__(self, key, value):
         """Same as for dict"""
         self._dict[key] = value
-        n = self._handler.send(dbus.Array([(dbus.Int32(CausalDict.ADD), self._key_trans(key, True), self._val_trans(value, True))]))
+        n = self.handler.send(dbus.Array([(dbus.Int32(CausalDict.ADD), self._key_trans(key, True), self._val_trans(value, True))]))
         self._index_dict[key] = n
     
     def clear(self):
         """Same as for dict"""
         self._dict.clear()
         self._index_dict.clear()
-        n = self._handler.send(dbus.Array([(dbus.Int32(CausalDict.CLEAR))]))
+        n = self.handler.send(dbus.Array([(dbus.Int32(CausalDict.CLEAR))]))
         self._clear = n
     
     def pop(self, key, x=None):
@@ -932,7 +1087,7 @@ class CausalDict:
             r = self._dict.pop(key, x)
         
         if t:
-            n = self._handler.send(dbus.Array([(dbus.Int32(CausalDict.DELETE), self._key_trans(key, True))]))
+            n = self.handler.send(dbus.Array([(dbus.Int32(CausalDict.DELETE), self._key_trans(key, True))]))
             self._index_dict[key] = n
         
         return r
@@ -941,7 +1096,7 @@ class CausalDict:
         """Same as for dict"""
         p = self._dict.popitem()
         key = p[0]
-        n = self._handler.send(dbus.Array([(dbus.Int32(CausalDict.DELETE), self._key_trans(key, True))]))
+        n = self.handler.send(dbus.Array([(dbus.Int32(CausalDict.DELETE), self._key_trans(key, True))]))
         self._index_dict[key] = n
         return p
     
@@ -949,7 +1104,7 @@ class CausalDict:
         """Same as for dict"""
         if key not in self._dict:
             self._dict[key] = x
-            n = self._handler.send(dbus.Array([(dbus.Int32(CausalDict.ADD), self._key_trans(key, True), self._val_trans(value, True))]))
+            n = self.handler.send(dbus.Array([(dbus.Int32(CausalDict.ADD), self._key_trans(key, True), self._val_trans(value, True))]))
         self._index_dict[key] = n
     
     def update(*args,**kargs):
@@ -961,7 +1116,7 @@ class CausalDict:
             if (p[0] not in self._dict) or (self._dict[p[0]] != p[1]):
                 newpairs.append(p)
                 self._dict[p[0]] = p[1]
-        n = self._handler.send(dbus.Array([(dbus.Int32(CausalDict.ADD), self._key_trans(p[0], True), self._val_trans(p[1], True)) for p in newpairs]))
+        n = self.handler.send(dbus.Array([(dbus.Int32(CausalDict.ADD), self._key_trans(p[0], True), self._val_trans(p[1], True)) for p in newpairs]))
         
         for p in newpairs:
             self._index_dict[p[0]] = n
@@ -1002,15 +1157,15 @@ class CausalDict:
                 self._trigger(a,r)
 
     def get_history(self):
-        c = self._handler.index_trans(self._clear, True)
+        c = self.handler.index_trans(self._clear, True)
         d = dbus.Array([(self._key_trans(p[0], True), self._val_trans(p[1], True)) for p in self._dict.items()])
         i = dbus.Array([(self._key_trans(p[0], True), self._handler.index_trans(p[1], True)) for p in self._index_dict.items()])
         return dbus.Tuple((c,d,i))
     
     def add_history(self, hist):
-        c = self._handler.index_trans(hist[0], False)
+        c = self.handler.index_trans(hist[0], False)
         d = dict(((self._key_trans(p[0], False), self._val_trans(p[1], False)) for p in hist[1]))
-        i = [(self._key_trans(p[0], False), self._handler.index_trans(p[1], False)) for p in hist[2]]
+        i = [(self._key_trans(p[0], False), self.handler.index_trans(p[1], False)) for p in hist[2]]
         
         a = dict()
         r = dict()
diff --git a/gtk.py b/gtk.py
new file mode 100644
index 0000000..9f8cc97
--- /dev/null
+++ b/gtk.py
@@ -0,0 +1,26 @@
+import gtk
+import groupthink
+
+class RecentEntry(HandlerAcceptor, gtk.Entry):
+    """RecentEntry is an extension of gtk.Entry that, when attached to a group,
+    creates a unified Entry field for all participants"""
+    def __init__(self, text=""):
+        gtk.Entry.__init__(self, text)
+        self._text_changed_handler = self.connect('changed', self._local_change_cb)
+        self._recent = groupthink.Recentest(text, string_translator)
+        self._recent.register_listener(self._remote_change_cb)
+        
+    def _local_change_cb(self, widget):
+        self._recent.set_value(self.get_text())
+    
+    def set_handler(self, handler):
+        self._recent.set_handler(handler)
+    
+    def _remote_change_cb(self, text):
+        if self.get_text() != text:
+            #The following code will break if running in any thread other than
+            #the main thread.  I do not know how to make code that works with
+            #both multithreaded gtk _and_ single-threaded gtk.
+            self._name.handler_block(self._name_changed_handler)
+            self.set_text(text)
+            self._name.handler_unblock(self._name_changed_handler)
diff --git a/sugar.py b/sugar.py
new file mode 100644
index 0000000..1993222
--- /dev/null
+++ b/sugar.py
@@ -0,0 +1,200 @@
+# Copyright 2007 Collabora Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import logging
+import telepathy
+
+from sugar.activity.activity import Activity, ActivityToolbox
+from sugar.presence import presenceservice
+
+from sugar.presence.tubeconn import TubeConnection
+
+import gtk
+
+import groupthink
+
+class GroupActivity(Activity):
+
+    message_preparing = "Preparing user interface"
+    message_loading = "Loading object from Journal"
+    message_joining = "Joining shared activity"
+    
+    """Abstract Class for Activities using Groupthink"""
+    def __init__(self, handle, service_name):
+        Activity.__init__(self, handle)
+        self.logger = logging.getLogger(service_name)
+        self.dbus_name = service_name
+        
+        ##gobject.threads_init()
+
+        # top toolbar with share and close buttons:
+        toolbox = ActivityToolbox(self)
+        self.set_toolbox(toolbox)
+        toolbox.show()
+
+        # This variable indicates whether this instance has initiated sharing
+        # it is always false, initially, and only becomes true if the user
+        # shares the activity after launch
+        self.initiating = False
+        
+        # This variable tracks whether the Activity's display is up and running
+        self.initialized = False
+        if self._shared_activity:
+            self.message = self.message_joining
+        elif handle.object_id:
+            self.message = self.message_loading
+        else:
+            self.message = self.message_preparing
+        
+        v = gtk.VBox()
+        self.startup_label = gtk.Label(self.message)
+        v.pack_start(self.startup_label)
+        self.set_canvas(v)
+        self.show_all()
+
+        self.tubebox = groupthink.TubeBox()
+        self.timer = groupthink.TimeHandler("main", self.tubebox)
+        self.group = groupthink.Group(self.tubebox)
+        # self.group is extremely important.  It is the unified reference point
+        # that contains all state in the system.  Everything else is stateless.
+
+        # get the Presence Service
+        self.pservice = presenceservice.get_instance()
+        # Buddy object for you
+        owner = self.pservice.get_owner()
+        self.owner = owner
+
+        self.connect('shared', self._shared_cb)
+        self.connect('joined', self._joined_cb)
+        
+        self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
+        self.connect("visibility-notify-event", self._visible_cb)
+        self.connect("notify::active", self._active_cb)
+        
+        if not (self._shared_activity or handle.object_id):
+            self.initialize_firststart()
+            self.initialize_display()
+    
+    def initialize_firststart(self):
+        """Any subclass that needs to take any extra action in the case where
+        the activity is launched locally without a sharing context or input
+        file should override this method"""
+        pass
+    
+    def initialize_display(self):
+        """All subclasses should override this method, in order to display
+        their GUI."""
+        self.initialized = True
+
+    def _shared_cb(self, activity):
+        self.logger.debug('My activity was shared')
+        self.initiating = True
+        self._sharing_setup()
+
+        self.logger.debug('This is my activity: making a tube...')
+        id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
+            self.dbus_name, {})
+
+    def _sharing_setup(self):
+        if self._shared_activity is None:
+            self.logger.error('Failed to share or join activity')
+            return
+
+        self.conn = self._shared_activity.telepathy_conn
+        self.tubes_chan = self._shared_activity.telepathy_tubes_chan
+        self.text_chan = self._shared_activity.telepathy_text_chan
+
+        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
+            self._new_tube_cb)
+
+    def _list_tubes_reply_cb(self, tubes):
+        for tube_info in tubes:
+            self._new_tube_cb(*tube_info)
+
+    def _list_tubes_error_cb(self, e):
+        self.logger.error('ListTubes() failed: %s', e)
+
+    def _joined_cb(self, activity):
+        if not self._shared_activity:
+            return
+
+        self.logger.debug('Joined an existing shared activity')
+        self.initiating = False
+        self._sharing_setup()
+        
+        if not self.initialized:
+            self.initialize_display()
+
+        self.logger.debug('This is not my activity: waiting for a tube...')
+        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+            reply_handler=self._list_tubes_reply_cb,
+            error_handler=self._list_tubes_error_cb)
+
+    def _new_tube_cb(self, id, initiator, type, service, params, state):
+        self.logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
+                     'params=%r state=%d', id, initiator, type, service,
+                     params, state)
+        if (type == telepathy.TUBE_TYPE_DBUS and
+            service == self.dbus_name):
+            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
+            tube_conn = TubeConnection(self.conn,
+                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
+                id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+            self.tubebox.insert_tube(tube_conn, self.initiating)
+    
+    """
+    #These functions require modifications to dbus-python to expose
+    #dbus_message_marshal and dbus_message_demarshal.
+    def read_file(self, file_path):
+        # This function should be implemented by deserializing a bunch of
+        # dbus messages derived from all the objects in self.group, and calling
+        # add_history on a matching set of blank objects to bring them up to
+        # present.
+        if not self.initialized:
+            self.initialize_display()
+        pass
+    
+    def write_file(self, file_path):
+        # This method should be implemented by serializing the results of
+        # .get_history() from each object in self.group, along with the name
+        # and type of that object.
+        pass
+    """
+        
+    def _active_cb(self, widget, event):
+        self.logger.debug("_active_cb")
+        if self.props.active:
+            self.resume()
+        else:
+            self.pause()
+            
+    def _visible_cb(self, widget, event):
+        self.logger.debug("_visible_cb")
+        if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED:
+            self.pause()
+        else:
+            self.resume()
+    
+    def pause(self):
+        """Subclasses should override this function to stop updating the display
+        since it is not visible."""
+        pass
+    
+    def resume(self):
+        """Subclasses should override this function to resume updating the
+        display, since it is now visible"""
+        pass
-----------------------------------------------------------------------


--
/home/bemasc/public_git/groupthink


More information about the Commits mailing list