Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sharedstate.git/sharedstate/sharedpython.py
diff options
context:
space:
mode:
Diffstat (limited to 'sharedstate.git/sharedstate/sharedpython.py')
-rw-r--r--sharedstate.git/sharedstate/sharedpython.py234
1 files changed, 234 insertions, 0 deletions
diff --git a/sharedstate.git/sharedstate/sharedpython.py b/sharedstate.git/sharedstate/sharedpython.py
new file mode 100644
index 0000000..78a622a
--- /dev/null
+++ b/sharedstate.git/sharedstate/sharedpython.py
@@ -0,0 +1,234 @@
+# 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