diff options
-rw-r--r-- | sharedstate.git/MANIFEST | 1 | ||||
-rw-r--r-- | sharedstate.git/SharingTest.py | 113 | ||||
-rw-r--r-- | sharedstate.git/activity/activity-shtest.svg | 8 | ||||
-rw-r--r-- | sharedstate.git/activity/activity.info | 7 | ||||
-rw-r--r-- | sharedstate.git/setup.py | 3 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/README | 10 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/__init__.py | 1 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/shareddict.py | 107 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/sharedobject.py | 436 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/sharedobjects.py | 25 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/sharedpython.py | 234 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/sharedstate.py | 532 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/sharedtext.py | 181 | ||||
-rw-r--r-- | sharedstate.git/sharedstate/tubeconn.py | 107 |
14 files changed, 0 insertions, 1765 deletions
diff --git a/sharedstate.git/MANIFEST b/sharedstate.git/MANIFEST deleted file mode 100644 index a81177f..0000000 --- a/sharedstate.git/MANIFEST +++ /dev/null @@ -1 +0,0 @@ -SharingTest.py diff --git a/sharedstate.git/SharingTest.py b/sharedstate.git/SharingTest.py deleted file mode 100644 index c8e0a0c..0000000 --- a/sharedstate.git/SharingTest.py +++ /dev/null @@ -1,113 +0,0 @@ -from sugar.activity.activity import Activity, ActivityToolbox - -import logging -_logger = logging.getLogger('shtest-activity') - -from sharedstate.sharedstate import SharingHelper - -from gettext import gettext as _ - -import pygtk -pygtk.require('2.0') -import gtk - -class SharingTest(Activity): - def __init__(self, handle): - Activity.__init__(self, handle) - - _logger.info('Starting SharingHelper test...') - - toolbox = ActivityToolbox(self) - self.set_toolbox(toolbox) - toolbox.show() - - self.set_title('SharingTest') - - hc = gtk.HBox(spacing=20) - hc.set_border_width(20) - vc1 = gtk.VBox() - self.dict_box = gtk.TextView() # where the current dict is shown - self.dict_box.set_editable(False) - vc1.add(self.dict_box) - hc2 = gtk.HBox(spacing=20) - hc2.set_border_width(20) - - self.dict_key_box = gtk.Entry() # new key => val boxes - self.dict_key_box.set_size_request(100, 40) - hc2.add(self.dict_key_box) - self.dict_val_box = gtk.Entry() - self.dict_val_box.set_size_request(100, 40) - hc2.add(self.dict_val_box) - vc1.add(hc2) - b2 = gtk.Button('add to dict') # add button - b2.connect('clicked', self.add_button_clicked_cb) - vc1.add(b2) - hc.add(vc1) - - vc2 = gtk.VBox() - b1 = gtk.Button('Counter++') # counter button - b1.connect('clicked', self.counter_button_clicked_cb) - vc2.add(b1) - self.CounterLabel = gtk.Label('0') # counter label - vc2.add(self.CounterLabel) - - self.edit_box = gtk.TextView() # shared editable text - self.edit_box.set_editable(True) - self.changed_signal = self.edit_box.get_buffer().connect('changed', self.edit_box_changed_cb) - vc2.add(self.edit_box) - - hc.add(vc2) - self.get_child().add(hc) - -# Setup the shared objects - self.helper = SharingHelper(self) - self.helper.create_shared_object('counter', { - 'changed': self.update_counter_cb - }, iv=0) - - self.helper.create_shared_object('sdict', { - 'changed': self.update_dict_cb - }, iv={'key1': 'first key/val'}) - - self.helper.create_shared_object('stext', { - 'changed': self.update_text_cb, - 'type': 'python' # or nothing to autotype to SharedText - }, iv='start of shared text') - -# And show everything - self.show_all() - - def counter_button_clicked_cb(self, info): - _logger.debug('Counter clicked') - self.helper['counter'] = self.helper['counter'] + 1 - - def update_counter_cb(self, val): - _logger.debug('update_counter_cb(): %r', val) - self.CounterLabel.set_text('%d' % (val)) - - def add_button_clicked_cb(self, info): - _logger.debug('Add clicked') - self.helper['sdict'][self.dict_key_box.get_text()] = self.dict_val_box.get_text() - self.dict_key_box.set_text('') - self.dict_val_box.set_text('') - - def update_dict_cb(self, val): - _logger.debug('update_dict_cb(): %r', val) - str = '' - for k, v in val.iteritems(): - str += "%s => %s\n" % (k, v) - self.dict_box.get_buffer().set_text(str) - - def edit_box_changed_cb(self, tbuf): - _logger.debug('text_changed_cb(): changed') - self.helper['stext'] = tbuf.get_text(tbuf.get_start_iter(), tbuf.get_end_iter()) - - def update_text_cb(self, val): - _logger.debug('update_text_cb(): %s', val) - buf = self.edit_box.get_buffer() -# pos = buf.get_iter_at_offset(buf.get_property('cursor_position')) - buf.handler_block(self.changed_signal) - buf.set_text(val) - buf.handler_unblock(self.changed_signal) -# buf.place_cursor(pos) - diff --git a/sharedstate.git/activity/activity-shtest.svg b/sharedstate.git/activity/activity-shtest.svg deleted file mode 100644 index 8c79234..0000000 --- a/sharedstate.git/activity/activity-shtest.svg +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ - <!ENTITY fill_color "#FFFFFF"> - <!ENTITY stroke_color "#000000"> -]> -<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50"> - <rect x="11" y="11" width="26" height="26" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> -</svg> diff --git a/sharedstate.git/activity/activity.info b/sharedstate.git/activity/activity.info deleted file mode 100644 index 1c37f71..0000000 --- a/sharedstate.git/activity/activity.info +++ /dev/null @@ -1,7 +0,0 @@ -[Activity] -name = SharingTest -service_name = org.laptop.SharingTest -class = SharingTest.SharingTest -icon = activity-shtest -activity_version = 1 -show_launcher = yes diff --git a/sharedstate.git/setup.py b/sharedstate.git/setup.py deleted file mode 100644 index ec0f64e..0000000 --- a/sharedstate.git/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python -from sugar.activity import bundlebuilder -bundlebuilder.start() diff --git a/sharedstate.git/sharedstate/README b/sharedstate.git/sharedstate/README deleted file mode 100644 index 229cefd..0000000 --- a/sharedstate.git/sharedstate/README +++ /dev/null @@ -1,10 +0,0 @@ -class SharedObject - -options: - -incremental (boolean): - default encoding style - -contiguous (boolean): [NOT YET IMPLEMENTED] - whether only contiguous updates are allowed - -dynamic (boolean): [PARTIALLY IMPLEMENTED] - whether this is a dynamically created object - diff --git a/sharedstate.git/sharedstate/__init__.py b/sharedstate.git/sharedstate/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/sharedstate.git/sharedstate/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/sharedstate.git/sharedstate/shareddict.py b/sharedstate.git/sharedstate/shareddict.py deleted file mode 100644 index 1a51a12..0000000 --- a/sharedstate.git/sharedstate/shareddict.py +++ /dev/null @@ -1,107 +0,0 @@ -# shareddict.py, classes to aid activities in sharing a state -# Reinier Heeres, reinier@heeres.eu -# -# 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 -# -# Change log: -# 2007-06-21: rwh, first version - -import logging -_logger = logging.getLogger('sharinghelper') - -from sharedobject import SharedObject - -class SharedDict(SharedObject): - """Shared dictionary object, generates key-based difference objects""" - - def __init__(self, name, helper, opt={}): - SharedObject.__init__(self, name, helper, opt=opt) - self._value = {} - - def __getitem__(self, key): -# _logger.debug('SharedDict.__getitem__(%s)', key) - return self.get_key(key) - - def __setitem__(self, key, val): -# _logger.debug('SharedDict.__setitem__(%s, %s)', key, val) - return self.set_key(key, val) - - def __delitem__(self, key): - self.delete_key(key) - - def get_key(self, key, val): - return self._value[key] - - def set_key(self, key, val): - if key in self._value: - d = {'change': {key: val}} - else: - d = {'add': {key: val}} - self._value[key] = val - self.changed(d, True) - - def delete_key(self, key): - if key in self._value: - del self._value[key] - d = {'remove': key} - self.changed(d, True) - - def _compatible_diffs(self, diffa, diffb): - for key in diffa: - if key in diffb: - return False - return True - - def diff(self, cur, old): - ret = {'remove': [], 'add': {}, 'change': {}} - if old is None: - return ret - - for key in set(old.keys()).difference(cur.keys()): - ret['remove'].append(key) - - for key in set(cur.keys()).difference(old.keys()): - ret['add'][key] = cur[key] - - for key in set(cur.keys()).intersection(old.keys()): - if cur[key] is not old[key]: - ret['change'][key] = cur[key] - - return ret - - def _apply_diff_to(self, obj, diffobj): - """Apply a diff and return an object that describes the inverse diff""" - - if diffobj is None: - return (None, None) - - ret = {'add':{}, 'remove':[], 'change':{}} - - if 'remove' in diffobj: - for key in diffobj['remove']: - ret['add'][key] = obj[key] - del self._value[key] - - if 'add' in diffobj: - for key, val in diffobj['add'].iteritems(): - ret['remove'].append(key) - obj[key] = val - - if 'change' in diffobj: - for key, val in diffobj['change'].iteritems(): - ret[key] = self._value[key] - obj[key] = val - - return (obj, ret) diff --git a/sharedstate.git/sharedstate/sharedobject.py b/sharedstate.git/sharedstate/sharedobject.py deleted file mode 100644 index 97472fc..0000000 --- a/sharedstate.git/sharedstate/sharedobject.py +++ /dev/null @@ -1,436 +0,0 @@ -# sharedobject.py, classes to aid activities in sharing a state -# Reinier Heeres, reinier@heeres.eu -# Miguel Alvarez, miguel@laptop.org -# -# 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 -# -# Change log: -# 2007-07-14: rwh, the big merge. Old function are kept with _nc -# (non-contiguous) appended -# 2007-07-07: miguel, conflict resolution added -# 2007-06-21: rwh, first version - -import copy -import pickle -import base64 -import difflib -import time - -import logging -_logger = logging.getLogger('sharinghelper') - -class DiffRec: - def __init__(self, versionid, sender, incremental, objstr): - self.version_id = versionid - self.sender = sender - self.incremental = incremental - self.obj = objstr - - def is_newer(self, v2): - if self.version_id > v2.version_id or \ - (self.version_id == v2.version_id and self.sender > v2.sender): - return True - else: - return False - - def __str__(self): - return "[%d]%s" % (self.version_id, str(self.obj)) - - def __repr__(self): - return self.__str__() - -class SharedObject: - """Base class for shared objects, able to share python objects the - dumb way""" - - def __init__(self, name, helper, opt = {}): - self._name = name - self._options = opt - self._helper = helper - self._value = None - self._version_id = 0 - self._received_diffs = [] - self._inverse_diffs = [] - self._pending_diffs = {} - self._cached_versions = 8 - self._locked = False - self._locked_by = None - self._locked_time = 0 - - def encode(self, obj): - return base64.b64encode(pickle.dumps(obj)) - - def decode(self, obj): - return pickle.loads(base64.b64decode(obj)) - - def _should_encode_incremental(self, incremental): - """Decide whether to encode an object incrementally. If nothing is - specified (incremental=None) determine from self._options, else - return what is specified.""" - - if incremental is None: - if 'incremental' in self._options and self._options['incremental'] is True: - return True - else: - return False - return incremental - - def changed_nc(self, diffobj, incremental): - """This function should be called when the object has changed.""" - - self._version_id += 1 - try: - diff = DiffRec(self._version_id, self._helper._own_bus_name, incremental, diffobj) - self.insert_diff(diff) - enc = self.encode(diff.obj) - if self._helper.tube_connected(): - self._helper.SendObject(self._name, self._version_id, incremental, enc) - except Exception, inst: - _logger.error('changed(): %s', inst) - - self.do_changed_callback() - - def changed(self, diffobj, incremental=False): - """This function should be called when the object has change - If diffobj is None the whole object will be sent - If defined, only this difference will be sent - A diff entry is also added to our received_diffs so that - we can properly sequence and undo/redo this change. - """ - - try: - self.insert_diff(DiffRec(self._version_id+1, - self._helper._own_bus_name, incremental, diffobj)) - if not incremental: - enc = self.encode(self._value) - else: - enc = self.encode(diffobj) - self._helper.SendObject(self._name, self._version_id, incremental, enc) - _logger.debug("Modification sent") - except Exception, inst: - _logger.error('changed(): %s, currval: %s', inst, self._value) - self.do_changed_callback() - - def set_value_nc(self, v, incremental=None): - """Function to set value of this object. Specifying incremental - (a boolean) allows forcing of either incremental/full encoding. - If not specified, the default will be used from self._options""" - - incremental = self._should_encode_incremental(incremental) - _logger.debug('Setting value of %s to %s, incremental=%s', self._name, v, incremental) - if incremental: - old = copy.deepcopy(self._value) - self._value = v - d = self.diff(v, old) - _logger.debug('set_value(): generated diff:\n%r', d) - del old - if d is not None: - self.changed(d, True) - else: - self._value = v - self.changed_nc(v, False) - - def set_value(self, v, incremental=False): - incremental = self._should_encode_incremental(incremental) - _logger.debug('Setting value of %s to %s, incremental=%s', self._name, v, incremental) - old = copy.deepcopy(self._value) - d = self.diff(v, old) - _logger.debug("Diff= %s", d) - self.changed(d, incremental) - - def get_value(self): - return self._value - - def do_changed_callback(self): - if 'changed' in self._options: - self._options['changed'](self._value) - - def output_diff_stack(self): - _logger.debug('Diff stack:') - for versionid, incremental, objstr, sender in self._received_diffs: - _logger.debug('\tv:%d, inc:%d, sender:%d', versionid, - incremental, sender) - - def get_version(self, versionid): - if versionid == self._version_id: - return self._value - if versionid > self._version_id or versionid < 0: - return None - target_index = self._get_diff_index(versionid + 1) #versionid of a diff== the version it leads _up_ to - if 0 > target_index or index > len(self._received_diffs): - return None - object = self._value - for i in range(len(self._received_diffs)-1, target_index -1, -1): - d = self._received_diffs[i] - object = self._apply_diff_to(object, self.inverse_diff(diff)) - return object - - def get_version(self, versionid): - if versionid == self._version_id: - return self._value - if versionid > self._version_id or versionid < 0: - return None - if versionid < self._received_diffs[0].version_id: - return None - - i = len(self._received_diffs) - 1 - while self._version_id > versionid: - obj = self._apply_diff_to(obj, self.inverse_diff(diff)) - i -= 1 - - return obj - - def inverse_diff(self, diff): - """Invert a diff object, so that if was the result of an o -> n comparison, the - result is associated to a n -> o comparison.""" - index = self._get_diff_index(diff) - if 0 < index < len(self._inverse_diffs): #found (?) - return self._inverse_diffs[index] - return None - - def _get_diff_index(self, vi): - for i in xrange(len(self._received_diffs)-1, -1 , -1): - if self._received_diffs[i].version_id == vi: - return i - return -1 - - def insert_diff_nc(self, d): - if len(self._received_diffs) > 0 and self._received_diffs[0].is_newer(d): - return -1 - - if len(self._received_diffs) >= self._cached_versions: - del(self._received_diffs[0]) - - i = len(self._received_diffs) - 1 - while i > -1: - if d.is_newer(self._received_diffs[i]): - break - i -= 1 - - self._received_diffs.insert(i + 1, d) - self._inverse_diffs.insert(i + 1, DiffRec(d.version_id, d.sender, d.incremental, None)) - - return i + 1 - - def insert_diff(self, recv_diff, old=None): - """ Places a new and compatible change of incremental type in its correct place, and - actualizes thevalue of the shared object accordingly.""" - _logger.debug("insert_diff(): Current versionid:%s, recv_diff:%s", self._version_id, recv_diff) - if not recv_diff.incremental: - _logger.debug("insert_diff(): Applying non-incremental diff") - if recv_diff in self._received_diffs: - _logger.debug("Dupe") - return True - recvi = recv_diff.version_id - index = self._get_diff_index(recvi) - if old == None: - old = self.get_version(recvi - 1) - #if old == None: #can't find the father, doesn't use - # self._discarded_diff = recv_diff - # #TODO: signal that we have a discarded diff --> return False - # return False - - _logger.debug("insert_diff(): old=%s", old) - if index < 0: - diffs = [] - else: - diffs = self._received_diffs[index:] - i = 0 - while len(diffs) > 0 and recv_diff.is_newer(diffs[0]): - diffs = diffs[1:] - index += 1 - i += 1 - recv_diff.version_id += i - for d in diffs: - d.version_id += 1 - res = [recv_diff] + diffs - - ret = old - invdiffs = [] - for d in res: - _logger.debug("[insert_diff] Applying %s to %s", d.obj, ret) - ret, id = self._apply_diff_to(ret, d.obj) - invdiffs.append(id) - self._value = ret - if index >= 0: - self._received_diffs = self._received_diffs[:index] + res - self._inverse_diffs = self._inverse_diffs[:index] + invdiffs - _logger.info("insert_diff(): recv_diff inserted at %d", index) - else: - self._received_diffs = self._received_diffs + res - self._inverse_diffs = self._inverse_diffs + invdiffs - _logger.debug("insert_diff(): recv_diff inserted at %d", len(self._received_diffs)) - self._version_id += 1 - if not self._version_id == self._received_diffs[-1].version_id: - _logger.debug("Version inconsistency: expected %s, got %s", self._received_diffs[-1], self._version_id) - self._version_id = self._received_diffs[-1].version_id - _logger.debug("new version_id: %s, actualized received_diffs: %s", self._version_id, self._received_diffs) - return True - - def process_update_nc(self, versionid, incremental, objstr, sender, force=False): - """Process an update: - -Undo all newer diffs - -Apply the just added one - -Redo all newer diffs - """ - - obj = self.decode(objstr) - d = DiffRec(versionid, sender, incremental, obj) - - i = self.insert_diff(d) - _logger.debug('Inserted diff at position %d', i) - if i == -1: - return False - - j = len(self._received_diffs) - 1 # Don't include ourselve - while j > i: - if not self._received_diffs[j].incremental: - break # not necessary to continue beyond replacement object - self.apply_diff(self._inverse_diffs[j].obj) - j -= 1 - - while j < len(self._received_diffs): - if not self._received_diffs[j].incremental: - self._value = self._received_diffs[j].obj - else: - self._inverse_diffs[j].obj = self.apply_diff(self._received_diffs[j].obj) - self._version_id = self._received_diffs[j].version_id - j += 1 - - self.do_changed_callback() - - return True - - def process_update(self, versionid, incremental, objstr, sender, force=False): - """Process an update: - -Undo all newer diffs to get to the common version - - Check for compatability between the received update and the 'combined' newer diffs - - If compatible, insert the received one - - If not, apply the 'winning' one, and pass the other to the 'rejected' folder - We return 'True' if there is no conflict, and 'False' if there was one - """ - if (versionid == self._version_id): - _logger.debug("Maybe dupe? our versionid:%d, received:%d", self._version_id, versionid) - a = len(self._received_diffs) > 0 - b = (sender == self._received_diffs[-1].sender) - _logger.debug("Two tests: %s, %s", a, b) - if a and b: - return True #Dupe - obj = self.decode(objstr) - _logger.debug( "process_update: version: %s, obj: %s" % (versionid, obj)) - if versionid > self._version_id + 1 and incremental and not force: - #Disordered diffs. Store for later use - self._pending_diffs[versionid] = DiffRec(versionid, sender, incremental, obj) - for i in range(self._version_id+1, versionid): - #TODO : Call for missing diffs: - pass - _logger.debug("Disordered diffs. returning") - return True - old = self.get_version(versionid - 1) #supposed common ancestor - if not incremental: #we get the incremental version maually - obj2 = self.diff(obj, old) - else: - obj2 = obj - db = DiffRec(versionid, sender, True, obj2) - if versionid > self._version_id or force: #expected version number - if not incremental: - _logger.debug("Update in expected range, non incremental") - self._value = obj - self._received_diffs.append(db) - self._version_id = versionid - else: - _logger.debug("Update in expected range, incremental") - self.insert_diff(db, old) - while self._version_id + 1 in self._pending_diffs: - #We get the pending diff and apply it - pd = self._pending_diffs[self._version_id + 1] - if not pd.incremental: - self._value = pd.obj - pd.obj = self.diff(pd.obj, self._value) - pd.incremental = True - else: - self.insert_diff(pd, self._value) - self._received_diffs.append(pd) - self._version_id += 1 - del self._pending_diffs[self._version_id +1] - _logger.debug("Updated value: %s", self._value) - self.do_changed_callback() - return True - else: - _logger.debug("Conflicting versionids: mine:%d, his:%d" %(self._version_id, versionid)) - obj_da = self.diff(self._value, old) - if len(self._received_diffs)>0: - sender_a = self._received_diffs[-1].sender - else: - sender_a = None - da = DiffRec(self._version_id, sender_a, True, obj_da) - _logger.debug("Verifying compatibility") - if self._compatible_diffs(da.obj, db.obj): - _logger.debug("compatible diff") - if self.insert_diff(db, old): - #insert does take care of version actualization and updates self._version_id - _logger.debug("changed without error") - self.do_changed_callback() - return True - else: - _logger.debug("Error with insertion") - return False - - def _compatible_diffs(self, da, db): - return True - - def diff(self, new, old): - return None - - def apply_diff_to(self, obj, diffobj): - return (obj, diffobj) - - def apply_diff(self, diffobj): - (newobj, idiff) = self.apply_diff_to(self._value, diffobj) - self._value = newobj - return idiff - - def is_locked(self): - return self._locked - - def lock(self): - if not self._locked: - self._locked = True - self._locked_by = "me" - self._locked_time = time.time() - self._helper.LockObject(self._name, self._locked_time) - - def receive_lock(self, sender, when): - if not self._locked or \ - (self._locked and self._locked_time > when): - if self._locked and 'locklost' in self._options: - self._options['locklost']() - self._locked = True - self._locked_by = sender - self._locked_time = when - if 'locked' in self._options: - self._options['locked'](sender) - - def unlock(self): - if self._locked and self._locked_by is 'me': - self._helper.UnlockObject(self._name) - - def receive_unlock(self, sender): - if self._locked: - self._locked = False - self._locked_by = None - self._locked_time = 0 - if 'unlocked' in self._options: - self._options['unlocked'](sender) diff --git a/sharedstate.git/sharedstate/sharedobjects.py b/sharedstate.git/sharedstate/sharedobjects.py deleted file mode 100644 index 341005a..0000000 --- a/sharedstate.git/sharedstate/sharedobjects.py +++ /dev/null @@ -1,25 +0,0 @@ -# sharedobjects.py, wrapper for importing SharedObject types -# Reinier Heeres, reinier@heeres.eu -# -# 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 -# -# Change log: -# 2007-06-22: rwh, first version - -from sharedobject import SharedObject -from sharedpython import SharedPython -from shareddict import SharedDict -from sharedtext import SharedText - diff --git a/sharedstate.git/sharedstate/sharedpython.py b/sharedstate.git/sharedstate/sharedpython.py deleted file mode 100644 index 78a622a..0000000 --- a/sharedstate.git/sharedstate/sharedpython.py +++ /dev/null @@ -1,234 +0,0 @@ -# sharedpython.py, classes to aid activities in sharing a state -# @author: Miguel Angel Alvarez, miguel@laptop.org -# @author: Reinier Heeres -# -# 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 -# -# Change log: -# - -import pickle -import difflib -import logging -from sharedobject import DiffRec -_logger = logging.getLogger('sharedpython') - -from sharedobject import SharedObject - -class SharedPython(SharedObject): - - def __init__(self, name, helper, opt={}): - SharedObject.__init__(self, name, helper, opt=opt) - self._value = None - self._picklestr = '' - - def _divide_change(self, c): - """Separate the index and the string parts of the chage string""" - if ' ' in c: - i = c.index(' ') - return (c[:i], c[i+1:]) - else: - print c - return None - - def inverse_diff(self, diff): - """Return the inverse diff object""" - d = SharedObject.inverse_diff(self, diff) - if d == None: - obj = self._generate_inverse_diffobj(diff.obj) - return DiffRec(diff.version_id, diff.sender, True, obj) - - def _generate_inverse_diffobj(self, changes): - ret = [] - delta = 0 - last_num = -1 - last_n_in = -1 - - for c in changes: - n, s = self._divide_change(c) - l = len(s) - num = abs(int(n)) - if num == last_num: - n_in = last_n_in - else: - n_in = num + delta - last_num = num - last_n_in = n_in - - if c[0] == '+': - ret.append('-'+str(n_in)+' '+s) - delta += l - elif c[0] == '-': - ret.append('+'+str(n_in)+' '+s) - delta -= l - else: - print "Unknown line type:", c - ret = self._format_changes(ret) - return ret - - def _update_interval(self, i, cs): - """Get an 'exclusion interval' (where no other different edits are accepted) for the change cs[i]""" - c = cs[i] - n, d = self._divide_change(c) - n = int(n) - if n >= 0: - return (n+1, n+1) - else: - return (abs(n), abs(n) + len(d)) - - def _intersect(self, ia, ib): - """function that takes 2 intervals (2 element ordered int lists) and returns whether they - intersect and, if not, which one is bigger: - ret > 0 => ia bigger - ret < 0 => ib bigger - ret = 0 => intersection""" - assert ia[1] >= ia[0] and len(ia) == 2 - assert ib[1] >= ib[0] and len(ib) == 2 - if ia[0] > ib[1]: - return 1 #No _intersect, ia bigger - if ia[1] < ib[0]: - return -1 #No _intersect, ia smaller - return 0 #Intersect - - def _compatible_diffs(self, diff_a, diff_b): - """ It returns whether two change arrays act upon the same positions, and - cannot therefore be automatically merged without risking conflict""" - _logger.debug("_compatible_diffs(): a=%s, b=%s", diff_a, diff_b) - index_a = index_b = 0 - while index_a < len(diff_a) and index_b < len(diff_b): - interval_a = self._update_interval(index_a, diff_a) - interval_b = self._update_interval(index_b, diff_b) - d = self._intersect(interval_a, interval_b) - if d == 0 : -## if diff_a[index_a] not in diff_b and diff_b[index_b] not in diff_a: -## print "change a:'%s'\tchange_b:'%s'" % (diff_a[index_a], diff_b[index_b]) -## return False -## elif interval_a[1] > interval_b[1]: -## index_b += 1 -## else: -## index_a += 1 -## ATT: More restrictive version of compatible_diffs used right now - return False - elif d == 1: - index_b += 1 - elif d == -1: - index_a += 1 - else: - index_a += 1 - index_b += 1 - return True - - def _format_changes(self, changes): - last_sign = None - last_num = -1 - foreseen_index = -1 - res=[] - for c in changes: - sign = c[0] - n, s = self._divide_change(c) - number = abs(int(n)) - if sign == "+" and last_sign == "-" and (number == foreseen_index or number == last_num) : - res = res[:-1]+[sign +str(last_num)+" "+s, res[-1]] - last_sign = sign - foreseen_index = number + len(s) - last_num = number - else: - res.append(c) - last_sign = sign - foreseen_index = number + len(s) - last_num = number - return res - - def diff(self, new_object, old_object): - """Generate a change array from two python objects""" - _logger.debug("Diffing old:%s (type: %s), new: %s (type: %s)", old_object, type(old_object), - new_object, type(new_object)) - differ = difflib.Differ() - old = pickle.dumps(old_object) - new = pickle.dumps(new_object) -## _logger.debug('Old text: %s', old) -## _logger.debug(' New text:%s', new) - ret = [] - raw_delta = list(differ.compare(old, new)) -## _logger.debug('raw delta: %s', raw_delta) - pos = 0 - continuous = False - for r in raw_delta: - if r[:2] == "+ ": - if len(ret) > 0 and ret[-1][0] == "+" and continuous: - ret[-1] += r[2:] - #if 2 continuous additions occur, append at the end - else: - string = "+"+str(pos)+" "+r[2:] - ret.append(string) - continuous = True - elif r[:2] == "- ": - if len(ret) > 0 and ret[-1][0] == "-" and continuous: - ret[-1] += r[2:] #append at the end - else: - string = "-"+str(pos)+" "+r[2:] - ret.append(string) - continuous = True - pos += 1 # TODO: important change; verify - elif r[:2] == "? ": - pass - else: - continuous = False - pos += len(r) - 2 -## _logger.debug('Ret before format changes: %s', ret) - return self._format_changes(ret) - - def _apply_diff_to(self, object, diffobj): - """ Apply a diff to a given object and return the new version - In this case, the provided object gets modified, too.""" - old = pickle.dumps(object) - _logger.debug("_apply_diff_to(): old=%r, diffobj=%s", old, diffobj) - new = "" - pos = old_pos = 0 - for c in diffobj: - id, st = self._divide_change(c) - pos = abs(int(id)) - new += old[old_pos:pos] - if id[0] == "+": -## _logger.debug("_apply_diff_to(): adding %r", st) - new += st - elif id[0] == "-": -## _logger.debug("_apply_diff_to(): deleting %r", st) - if old[pos:pos+len(st)] != st: - exc = "Bad delete at %d, expected %s, got %r" % (pos, - st, old[pos:pos+len(st)]) - raise Exception(exc) - pos += len(st) - old_pos = pos - if pos < len(old): - new += old[pos:] - res = pickle.loads(new) - _logger.debug("_apply_diff_to(): new=%r, res=%s", new, res) - idiff = self._generate_inverse_diffobj(diffobj) - return (res, idiff) - - def get_version(self, versionid): - _logger.debug("get_version(): called with versionid = %d", versionid) - if versionid == self._version_id: - return self._value - ret = self._value - if versionid > self._version_id: - return None - for i in range(len(self._received_diffs)-1, self._get_diff_index(versionid), -1): - d = self._received_diffs[i] - idobj = self._generate_inverse_diffobj(d.obj) - ret = self._apply_diff_to(ret, idobj)[0] - _logger.debug("get_version(): return value: %s", ret) - return ret diff --git a/sharedstate.git/sharedstate/sharedstate.py b/sharedstate.git/sharedstate/sharedstate.py deleted file mode 100644 index c69ace0..0000000 --- a/sharedstate.git/sharedstate/sharedstate.py +++ /dev/null @@ -1,532 +0,0 @@ -# sharedstate.py, classes to aid activities in sharing a state -# Reinier Heeres, reinier@heeres.eu -# -# 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 -# -# Change log: -# 2007-05-22: rwh, first version - -"""General imports""" -import types -import copy - -import logging -_logger = logging.getLogger('sharinghelper') - -"""DBus imorts""" -import dbus -import dbus.service -import dbus.glib -import gobject - -"""Sugar imports""" -from sugar.presence import presenceservice -from sugar.presence import activity -from sugar.activity.activity import Activity - -"""Telepathy imports""" -import telepathy -import telepathy.client -from tubeconn import TubeConnection - -from sharedobjects import * - -IFACE = "org.laptop.SharingHelper" - -class SharingHelper(dbus.service.Object): - """Class to help activities with state sharing""" - - def __init__(self, actparent, opt={}): - self._activity = actparent - self._options = opt - self._shared_types = {} - self._shared_objects = {} - self._buddy_list = {} - self._service_name = '' - self._object_path = '' - self._tube = None - self._own_bus_name = None - - self.register_shared_type('int', SharedPython, inc=False, autotype=int) - self.register_shared_type('long', SharedPython, inc=False, autotype=long) - self.register_shared_type('float', SharedPython, inc=False, autotype=float) - self.register_shared_type('string', SharedText, inc=True, autotype=str) - self.register_shared_type('ustring', SharedText, inc=True, autotype=unicode) - self.register_shared_type('dict', SharedDict, inc=True, autotype=dict) - self.register_shared_type('python', SharedPython, inc=True) - - self._tp_support = TubePresenceSupport(self) - self._tp_support.connect_to_ps() - - return - - def __getitem__(self, key): - if type(key) != types.StringType: - raise TypeError, "SharingHelper.__getitem()__ only accepts string keys" - - if isinstance(self._shared_objects[key], SharedDict): - return self._shared_objects[key] - else: - return self._shared_objects[key].get_value() - - def __setitem__(self, key, val): - if type(key) != types.StringType: - raise TypeError, "SharingHelper.__setitem()__ only accepts string keys" - self._shared_objects[key].set_value(val) - - def get_object(self, key): - if type(key) != types.StringType: - raise TypeError, "SharingHelper.get_object() only accepts string keys" - self._shared_objects[key] - - def _tube_created_cb(self, tube, reqsync): - self._tube = tube - self._object_path = '/org/laptop/SharingHelper/%s' % (self._activity._shared_activity._id) - - if self._tube is None: - _logger.error('setup_shared_tube(): no tube connection yet!') - return False - - dbus.service.Object.__init__(self, self._tube, self._object_path) - - _logger.info('Connected to bus as %s, object path %s', self._service_name, self._object_path) - self._activity._shared_activity.connect('buddy-joined', self._buddy_joined_cb) - self._activity._shared_activity.connect('buddy-left', self._buddy_left_cb) - - self._tube.add_signal_receiver(self._receive_object, 'SendObject', \ - IFACE, path=self._object_path, sender_keyword='sender') - self._tube.add_signal_receiver(self._receive_new_object, 'SendNewObject', \ - IFACE, path=self._object_path, sender_keyword='sender') - self._tube.add_signal_receiver(self._receive_sync_request, 'RequestSync', \ - IFACE, path=self._object_path, sender_keyword='sender') - self._tube.add_signal_receiver(self._receive_message, 'SendMessage', \ - IFACE, path=self._object_path, sender_keyword='sender') - - _logger.info('Tube setup successful!') - - if reqsync: - self.synchronize() - if 'on_connect' in self._options: - self._options['on_connect']() - - return True - - def _buddy_joined_cb(self, activity, buddy): - """Callback for buddy joining""" - _logger.info('Buddy %s joined', buddy._properties["nick"]) - - key = buddy._properties["key"] - if key not in self._buddy_list: - self._buddy_list[key] = buddy - - def _buddy_left_cb(self, activity, buddy): - """Callback for buddy leaving""" - _logger.info('Buddy %s left', buddy) - - key = buddy._properties["key"] - if buddy in self._buddy_list: - del self._buddy_list[key] - - def get_buddy_list(self): - return self._buddy_list - - def tube_connected(self): - if self._tube is None: - return False - - return True - -########################################## -# Shared object collection managing functions -########################################## - - def register_shared_type(self, name, oclass, inc=1, autotype=None): - self._shared_types[name] = (oclass, inc, autotype) - - def add_shared_object(self, o): - """Add shared object to list of objects to process""" - self._shared_objects[o._name] = o - return True - - def create_shared_object(self, name, opt, iv=None): - """Create a new shared object""" - -# Auto-detect object type - if 'type' not in opt: - for key, (oclass, inc, autotype) in self._shared_types.iteritems(): - if autotype is not None and isinstance(iv, autotype): - opt['type'] = key - opt['incremental'] = inc - - _logger.debug("Shared object %s of type %s requested", name, opt['type']) - - if opt['type'] not in self._shared_types: - _logger.error("Shared object type %s unknown", opt['type']) - return None - - (oclass, inc, autotype) = self._shared_types[opt['type']] - obj = oclass(name, opt=opt, helper=self) - - if obj is None: - return None - - self.add_shared_object(obj) - -# Tell other instances about dynamically created objects - if 'dynamic' in opt and opt['dynamic'] is True: - self.SendNewObject(name, opt) - - if iv is not None: - obj.set_value(iv) - - return obj - -########################################## -# Sending and receiving objects -########################################## - - def is_remote_sender(self, sender): - if self._own_bus_name is None: - self._own_bus_name = self._tube.get_unique_name() - _logger.info('Acquired unique name: %s', self._own_bus_name) - - if sender == self._own_bus_name: - return False - else: - return True - - @dbus.service.signal(dbus_interface=IFACE, signature='subs') - def SendObject(self, name, versionid, incremental, objstr): - """Signal proxy to send an object""" - _logger.info('Sending object %s v%d on bus (inc=%d)', name, versionid, incremental) - - def _receive_object(self, name, versionid, incremental, objstr, sender=None): - """Response to SendObject() signal; updates the state of the shared object. - If an update is requested for an object that does not exist yet we - must be in a confused state, so ask for a sync. - """ - if not self.is_remote_sender(sender): - return True - - _logger.info('receive_object(): Received object: %s v%d (inc=%d) from %s, %s', \ - name, versionid, incremental, sender, objstr) - if name in self._shared_objects: - self._shared_objects[name].process_update(versionid, incremental, objstr, sender) - else: - _logger.error('receive_object(): Unknown object %s; requesting sync', name) - self.synchronize() - - @dbus.service.signal(dbus_interface=IFACE, signature='sa{sv}') - def SendNewObject(self, name, opts): - """Signal proxy to create a new object""" - _logger.info('Sending new object %s on bus', name) - - def _receive_new_object(self, name, opts, sender=None): - """Response to SendNewObject() signal; creates a new shared object. - If creation is requested for an object that exists already, leave - it untouched. - If the 'objectcreated' option is set call that function - """ - if not self.is_remote_sender(sender): - return True - - _logger.info('receive_new_object(): Received new object: %s from %s', name, sender) - if name in self._shared_objects: - _logger.error('receive_new_object(): object already exists; leaving untouched') - else: - obj = self.create_shared_object(name, opts) - if 'objectcreated' in self._options: - self._options['objectcreated'](obj) - - def synchronize(self): - self._in_sync = False - #self.RequestSync() - #gobject.timeout_add(1000, self._verify_sync) - - def _verify_sync(self): - if not self._in_sync: - self.RequestSync() - return True - return False - - @dbus.service.signal(dbus_interface=IFACE, signature='') - def RequestSync(self): - """Signal proxy to request synchronization""" - _logger.info('Sending synchronization request...') - - def _receive_sync_request(self, sender=None): - """Called when somebody sends a SyncRequest.""" - if not self.is_remote_sender(sender): - return True - - _logger.info('Received sync request from %s, sending objects', sender) - for name, obj in self._shared_objects.iteritems(): -# Sending options is only necessary for dynamically created objects; implement this later -# sendopt = copy.deepcopy(obj.Options) -# if 'changed' in sendopt: -# del sendopt['changed'] - self._tube.get_object(sender, self._object_path).ReceiveSyncObject( \ - name, {'empty': True}, obj._version_id, obj.encode(obj._value)) - - @dbus.service.method(dbus_interface=IFACE, in_signature='sa{sv}us', out_signature='', sender_keyword='sender') - def ReceiveSyncObject(self, name, opt, versionid, objstr, sender=None): - """Function to receive full synchronisation. Used when joining an - existing activity or when in a confused state. - If an object does not exists yet it is created; the version is forced - """ - _logger.debug('Receiving sync object %s from %s', name, sender) - - if not self.is_remote_sender(sender): - return True - - self._in_sync = True - - if name not in self._shared_objects: - obj = self.create_shared_object(name, opts) - - self._shared_objects[name].process_update(versionid, False, objstr, sender, force=True) - - @dbus.service.signal(dbus_interface=IFACE, signature='sd') - def LockObject(self, name, when): - """Signal proxy to request object lock""" - _logger.debug('Sending lock signal for %s', name) - - def _receive_lock_object(self, name, when, sender=None): - """Called when somebody tries to lock an object""" - if name in self._shared_objects: - self._shared_objects[name].receive_lock(sender, when) - else: - _logger.error('Received lock signal for non-existing object %s', name) - - @dbus.service.signal(dbus_interface=IFACE, signature='s') - def UnlockObject(self, name): - """Signal proxy to signal release of an object lock""" - _logger.debug('Sending unlock signal for %s', name) - - def _receive_unlock_object(self, name, sender=None): - """Called when somebody signals unlocking of an object""" - if name in self._shared_objects: - self._shared_objects[name].receive_unlock() - else: - _logger.error('Received unlock signal for non-existing object %s', name) - -########################################## -# Simple message sending between apps -########################################## - - @dbus.service.signal(dbus_interface=IFACE, signature='sv') - def SendMessage(self, msg, val): - """Signal proxy to send simple messages""" - - def send_message(self, msg, val, to=None): - _logger.debug('send_message(msg=%s, val=%r)', msg, val) - try: - if to is None: - self.SendMessage(msg, val) - except Exception, inst: - _logger.error('send_message: %s', inst) - - def _receive_message(self, msg, val, sender=None): - if not self.is_remote_sender(sender): - return True - - _logger.info('_receive_message(%s, %r)', msg, val) - - if 'receive_message' in self._options: - self._options['receive_message'](msg, val) - -########################################## -# Functions for turn-based activities -########################################## - -class TurnBased: - def __init__(self, helper): - self._helper = helper - self._helper.create_shared_object('_turntoken', { - 'locked': self.my_turn, - 'unlocked': self.turn_changed, - 'locklost': self.turn_problem, - }) - self._playing = False - self._playing_buddies = [] - self._watching_buddies = [] - - def set_number_of_players(self, minp, maxp=None): - self._started = False - self._min_players = minp - self._max_players = maxp - self.check_ready() - - def set_turn_callbacks(self, d): - """Set callbacks for turn-based functions: - ready: called when enough players to start - myturn: called when it's this instance's turn - """ - self._turn_callbacks = d - - def check_ready(self): - if len(self._helper.get_buddy_list()) >= self._min_players: - self._turn_callbacks['ready']() - self._started = True - - def start(self): - self._playing = True - self._playing_buddies = self._helper.get_buddy_list() - self._current_player = -1 - self.turn_changed(self, None) - - def who_is_next_player(self): - self._current_player = (self._current_player + 1) % len(self._playing_buddies) - return self._playing_buddies[self._current_player] - - def turn_changed(self, sender): - if sender is not None and 'processturn' in self._turn_callbacks: - self._turn_callbacks['processturn'](sender, self.get_turn_data()) - if self.who_is_next_player() == self._helper._own_dbus_name: - self._helper.get_object('_turntoken').lock() - if 'myturn' in self._turn_callbacks: - self._turn_callbacks['myturn']() - - def release_turn(self, turndata): - self._helper['_turndata'] = turndata - self._helper.get_object('_turntoken').unlock() - - return - - def turn_problem(self, sender): - return - - - -########################################## -# Functions for Tube/Presence support -########################################## - -class TubePresenceSupport: - - def __init__(self, parent): - self._parent = parent - self._activity = parent._activity - - self._request_sync = False - - return - - def connect_to_ps(self): - """Connect to the presence service""" - - self._activity.connect('shared', self._shared_cb) - - self._ps = presenceservice.get_instance() - if self._activity._shared_activity: - # We are trying to join, call this if it worked - self._activity.connect('joined', self._joined_cb) - if self._activity.get_shared(): - self.joined_cb() - - def setup_shared_activity(self): - """Setup things to talk to other SharingHelpers """ - - if self._activity._shared_activity is None: - _logger.error('setup_shared_activity(): no _shared_activity yey!') - return False - - self._service_name = 'org.laptop.SharingHelper' - -# Get basic telepathy stuff - name, path = self._ps.get_preferred_connection() - _logger.info('Preferred connection: name %s, path %s', name, path) - self._tp_conn_name = name - self._tp_conn_path = path - self._tp_conn = telepathy.client.Connection(name, path) - -# Setup tubes channel - self._tube_service_name = '%s.Tube' % (self._service_name) - bus_name, conn_path, channel_paths = self._activity._shared_activity.get_channels() - room = None - self._text_channel = None - self._tube_channel = None - for channel_path in channel_paths: - channel = telepathy.client.Channel(bus_name, channel_path) - htype, handle = channel.GetHandle() - if htype == telepathy.HANDLE_TYPE_ROOM: - _logger.info('Found room with handle %d', handle) - room = handle - ctype = channel.GetChannelType() - if ctype == telepathy.CHANNEL_TYPE_TUBES: - _logger.info('Found Tubes channel %s', channel_path) - self._tube_channel = channel - elif ctype == telepathy.CHANNEL_TYPE_TEXT: - _logger.info('Found Text channel %s', channel_path) - self._text_channel = channel - - if room is None: - _logger.error('Didn\'t find room') - - if self._tube_channel is None: - self._tube_channel = self._tp_conn.request_channel( \ - telepathy.CHANNEL_TYPE_TUBES, \ - telepathy.HANDLE_TYPE_ROOM, room, True) - - self._tube_channel[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', self._new_tube_cb) - - def _shared_cb(self, activity): - """Callback for when our activity is shared""" - _logger.info('Activity shared') - self.setup_shared_activity() - self._tube_channel[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( \ - self._tube_service_name, {}) - - def _joined_cb(self, activity): - """Callback for when we join an existing activity""" - _logger.info('Joined existing activity') - self._request_sync = True - self.setup_shared_activity() - - _logger.info('Getting tubes list...') - self._tube_channel[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): - """Callback for when a new tube is created""" - _logger.info('new_tube_cb(): id=%d, init=%d, type=%d, svc=%s, state=%d, _request_sync=%r', id, initiator, type, service, state, self._request_sync) - _logger.info('Expected: type=%d, svc=%s, state=%d', telepathy.TUBE_TYPE_DBUS, self._tube_service_name, telepathy.TUBE_STATE_LOCAL_PENDING) - if type == telepathy.TUBE_TYPE_DBUS and service == self._tube_service_name: - if state == telepathy.TUBE_STATE_LOCAL_PENDING: - self._tube_channel[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id) - self._tube = TubeConnection(self._tp_conn, \ - self._tube_channel[telepathy.CHANNEL_TYPE_TUBES], id, \ - group_iface=self._text_channel[telepathy.CHANNEL_INTERFACE_GROUP]) - - if self._tube is None: - _logger.error('Don\'t have a tube channel, not connecting') - return False - - self._parent._tube_created_cb(self._tube, self._request_sync) - - def _list_tubes_reply_cb(self, tubes): - """Callback for when requesting an existing tube""" - _logger.debug('_list_tubes_reply_cb(): %r', tubes) - for tube_info in tubes: - _logger.debug('tube_info: %r', tube_info) - self._new_tube_cb(*tube_info) - - def _list_tubes_error_cb(self, e): - _logger.error('list_tubes() failed: %s', e) - - def _tube_participant_change_cb(self, added, removed): - _logger.info('Adding participants: %r', added) - _logger.info('Removing participants: %r', removed) diff --git a/sharedstate.git/sharedstate/sharedtext.py b/sharedstate.git/sharedstate/sharedtext.py deleted file mode 100644 index 384f0a8..0000000 --- a/sharedstate.git/sharedstate/sharedtext.py +++ /dev/null @@ -1,181 +0,0 @@ -# sharedobjects.py, classes to aid activities in sharing a state -# Reinier Heeres, reinier@heeres.eu -# -# 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 -# -# Change log: -# 2007-06-21: rwh, first version - -import types -import difflib -import logging -_logger = logging.getLogger('sharinghelper') - -from sharedobject import SharedObject - -def my_joinlines(list, addsep=False, separator='\n'): - if len(list) == 0: - return '' - string = list[0] - for s in list[1:]: - if addsep: - string += separator - string += s - return string - -def my_splitlines(str, keepsep=False, separators=['\n']): - list = [] - if str is None: - return list - ofs = 0 - startofs = 0 - while ofs < len(str): - if str[ofs] in separators: - if keepsep: - list.append(str[startofs:ofs+1]) - else: - if startofs != ofs: - list.append(str[startofs:ofs]) - else: - list.append('') - startofs = ofs + 1 - ofs += 1 - return list - -def string_to_list(str): - list = [] - if str is not None: - for i in str: - list.append(i) - return list - -def list_to_string(l): - str = "" - for i in l: - str += i - return str - -class SharedText(SharedObject): - """Shared text object, generates line-by-line difference objects""" - - REPLACE = 0 - DELETE = 1 - INSERT = 2 - - BY_CHAR = 0 - BY_WORD = 1 - BY_LINE = 2 - - def __init__(self, name, helper, opt={}, by_what=BY_CHAR): - SharedObject.__init__(self, name, helper, opt=opt) - self._value = '' - self._by_what = by_what - - def split(self, s): - if self._by_what == SharedText.BY_CHAR: - return string_to_list(s) - elif self._by_what == SharedText.BY_WORD: - return my_splitlines(s, keepsep=True, separators=['\n', ' ']) - elif self._by_what == SharedText.BY_LINE: - return my_splitlines(s, keepsep=True) - else: - _logger.error('SharedText.split(): unknown splitting type') - - def join(self, l): - if self._by_what == SharedText.BY_CHAR: - return list_to_string(l) - elif self._by_what == SharedText.BY_WORD: - return my_joinlines(l) - elif self._by_what == SharedText.BY_LINE: - return my_joinlines(l) - else: - _logger.error('SharedText.split(): unknown splitting type') - - def _compatible_diffs(self, da, db): - return True - - def diff(self, cur, old): - """Generate a difference object between to text objects""" - - _logger.debug('Generating diff between %r and %r', cur, old) - - l1 = self.split(cur) - l2 = self.split(old) - sm = difflib.SequenceMatcher(None, l2, l1) - ret = [] - for (tag, i1, i2, j1, j2) in sm.get_opcodes(): - if tag is 'replace': - ret.append((SharedText.REPLACE, (i1, i2, j1, j2), l1[j1:j2])) - elif tag is 'delete': - ret.append((SharedText.DELETE, (i1, i2, j1, j2), None)) - elif tag is 'insert': - ret.append((SharedText.INSERT, (i1, i2, j1, j2), l1[j1:j2])) - elif tag is 'equal': - pass - else: - _logger.warning('SharedText.diff(): unkown tag: %s', tag) - - if len(ret) is 0: - return None - else: - return ret - - def _apply_diff_to(self, obj, diffobj): - """Apply a diff and return an object that describes the inverse diff""" - - if diffobj is None: - _logger.error('SharedText.apply_diff_to(): no diffobj given') - return (None, None) - - _logger.debug('Applying %r to %r', diffobj, obj) - ret = [] - d = 0 - l2 = self.split(obj) - for (tag, (i1, i2, j1, j2), val) in diffobj: - i1 -= d - i2 -= d -# print 'd: %r' % (d) - if tag is SharedText.REPLACE: - ret.append((SharedText.REPLACE, (j1, j2, i1, i2), l2[i1:i2])) - l2[i1:i2] = val - d -= (j2 - j1) - (i2 - i1) - elif tag is SharedText.DELETE: - ret.append((SharedText.INSERT, (j1, j2, i1, i2), l2[i1:i2])) - del(l2[i1:i2]) - d += i2 - i1 - elif tag is SharedText.INSERT: - ret.append((SharedText.DELETE, (j1, j2, i1, i2), None)) - l2[i1:i1] = val - d -= j2 - j1 - - obj = self.join(l2) - return (obj, ret) - - - def insert(self, ofs, str): - self._value = self._value[:ofs] + str + self._value[ofs:] - dobj = {"type": 'chars', "data": (SharedText.INSERT, ofs, str)} - self.changed(dobj, True) - - def remove(self, ofs, len): - del self._value[ofs:ofs+len] - dobj = {"type": 'chars', "data": (SharedText.REMOVE, ofs, len)} - self.changed(dobj, True) - - def replace(self, ofs, str): - self._value[ofs:ofs+len(str)] = str - dobj = {"type": 'chars', "data": (SharedText.REPLACE, ofs, str)} - self.changed(dobj, True) - diff --git a/sharedstate.git/sharedstate/tubeconn.py b/sharedstate.git/sharedstate/tubeconn.py deleted file mode 100644 index b487391..0000000 --- a/sharedstate.git/sharedstate/tubeconn.py +++ /dev/null @@ -1,107 +0,0 @@ -# This should eventually land in telepathy-python, so has the same license: - -# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -__all__ = ('TubeConnection',) -__docformat__ = 'reStructuredText' - - -import logging - -from dbus.connection import Connection - - -logger = logging.getLogger('telepathy.tubeconn') - - -class TubeConnection(Connection): - - def __new__(cls, conn, tubes_iface, tube_id, address=None, - group_iface=None, mainloop=None): - if address is None: - address = tubes_iface.GetDBusTubeAddress(tube_id) - self = super(TubeConnection, cls).__new__(cls, address, - mainloop=mainloop) - - self._tubes_iface = tubes_iface - self.tube_id = tube_id - self.participants = {} - self.bus_name_to_handle = {} - self._mapping_watches = [] - - if group_iface is None: - method = conn.GetSelfHandle - else: - method = group_iface.GetSelfHandle - method(reply_handler=self._on_get_self_handle_reply, - error_handler=self._on_get_self_handle_error) - - return self - - def _on_get_self_handle_reply(self, handle): - self.self_handle = handle - match = self._tubes_iface.connect_to_signal('DBusNamesChanged', - self._on_dbus_names_changed) - self._tubes_iface.GetDBusNames(self.tube_id, - reply_handler=self._on_get_dbus_names_reply, - error_handler=self._on_get_dbus_names_error) - self._dbus_names_changed_match = match - - def _on_get_self_handle_error(self, e): - logging.basicConfig() - logger.error('GetSelfHandle failed: %s', e) - - def close(self): - self._dbus_names_changed_match.remove() - self._on_dbus_names_changed(self.tube_id, (), self.participants.keys()) - super(TubeConnection, self).close() - - def _on_get_dbus_names_reply(self, names): - self._on_dbus_names_changed(self.tube_id, names, ()) - - def _on_get_dbus_names_error(self, e): - logging.basicConfig() - logger.error('GetDBusNames failed: %s', e) - - def _on_dbus_names_changed(self, tube_id, added, removed): - if tube_id == self.tube_id: - for handle, bus_name in added: - if handle == self.self_handle: - # I've just joined - set my unique name - self.set_unique_name(bus_name) - self.participants[handle] = bus_name - self.bus_name_to_handle[bus_name] = handle - - # call the callback while the removed people are still in - # participants, so their bus names are available - for callback in self._mapping_watches: - callback(added, removed) - - for handle in removed: - bus_name = self.participants.pop(handle, None) - self.bus_name_to_handle.pop(bus_name, None) - - def watch_participants(self, callback): - self._mapping_watches.append(callback) - if self.participants: - # GetDBusNames already returned: fake a participant add event - # immediately - added = [] - for k, v in self.participants.iteritems(): - added.append((k, v)) - callback(added, []) |