示例#1
0
	def __init__(self, waveId, waveletId, contributorId):
		"""
		Initializes the op manager with a wave and wavelet ID.
		
		@constructor {public} initialize
		@param {String} waveId The ID of the wave
		@param {String} waveletId The ID of the wavelet
		@param {String} contributorId The ID of the contributor (= creator of the Delta)
		"""
		self.waveId = waveId
		self.waveletId = waveletId
		self.operations = Array()
		self.lockedBlips = Array()
		self.contributorId = contributorId
示例#2
0
	def unserialize(self, serial_ops):
		"""
		Unserialize a list of dictionaries to operations and add them to this
		manager.
		
		@function {public} unserialize
		@param {Object[]} serial_ops
		"""
		
		ops = Array()
		
		for op in serial_ops:
			ops.append(Operation.unserialize(op))
		
		self.put(ops)
示例#3
0
	def elementsWithin(self, start, end):
		"""
		Returns the Elements between the start and end index.
		
		@function {public Element[]} elementsWithin
		@param {int} start Start index
		@param {int} end End index
		"""
		
		lst = Array()
		for i in xrange(self._elements.length):
			elt = self._elements[i]
			if elt.position() >= start and elt.position() < end:
				lst.append(elt)
		
		return lst
示例#4
0
	def serialize(self, fetch = False):
		"""
		Serialize this manager's operations into a list of dictionaries.
		Set fetch to true to also clear this manager.
		
		@function {public Object[]} serialize
		@param {optional Boolean} fetch
		"""
		if fetch:
			ops = self.fetch()
		else:
			ops = self.operations
		
		out = Array()
		
		for op in ops:
			out.append(op.serialize())
		
		return out
示例#5
0
	def fetch(self):
		"""
		Returns the pending operations and removes them from this manager.
		
		@function {public Operation[]} fetch
		"""
		ops = Array()
		i = 0
		s = 0
		while i < len(self.operations):
			op = self.operations[i]
			if self.lockedBlips.contains(op.blipId):
				if i - s > 0:
					self.removeOperations(s, i-1)
					i -= s+1
				s = i+1
			else:
				ops.append(op)
			i += 1
		if i - s > 0:
			self.removeOperations(s, i-1)
		return ops
示例#6
0
	def loadBlipsFromSnapshot(self, blips, rootBlipId):
		"""
		Load the blips from a snapshot.
		@function {public} loadBlipsFromSnapshot
		@param {Object} blips The JSON-serialized snapshot to load
		@param {String} rootBlipId ID to identify the root Blip
		"""
		
		pp = self._wave.participantProvider()
		
		# Ordering
		created = Hash()
		for blip_id, blip in blips.iteritems():
			created.set(blip["creationTime"], blip_id)
		order = created.getKeys()
		order.sort()
		
		for timestamp in order:
			blip_id = created[timestamp]
			blip = blips[blip_id]
			contributors = Array()
			for cid in blip["contributors"]:
				contributors.append(pp.participant(cid))
			blip_options = {
				"is_root": blip_id == rootBlipId,
				"last_modified": blip["lastModifiedTime"],
				"version": blip["version"],
				"submitted": blip["submitted"]
			}
			blip_elements = []
			for serialelement in blip["elements"]:
				if serialelement["type"] == ELEMENT_TYPE["GADGET"]:
					blip_elements.append(GadgetElement(None, serialelement["id"], serialelement["index"], serialelement["properties"]))
				else:
					blip_elements.append(Element(None, serialelement["id"], serialelement["index"], serialelement["type"], serialelement["properties"]))
			blipObj = self.appendBlip(blip_id, blip_options, blip["content"], blip_elements, pp.participant(blip["creator"]), contributors)
示例#7
0
    def removeOperations(self, start, end):
        """
		Removes operations between and including the given start and end
		indexes. Fires signals appropriately.
		
		@function {public} removeOperations
		@param {int} start
		@param {int} end
		"""
        self.fireEvent("beforeOperationsRemoved", [start, end])
        if start == 0 and end == len(self.operations) - 1:
            self.operations = Array()
        else:
            for i in xrange(start, end + 1):
                self.operations.pop(start)
        self.fireEvent("afterOperationsRemoved", [start, end])
示例#8
0
class OpManager(object):
	"""
	Manages operations: Creating, merging, transforming, serializing.
	
	The operation manager wraps single operations as functions and generates
	operations in-order. It keeps a list of operations and allows
	transformation, merging and serializing.
	
	An OpManager is always associated with exactly one wave/wavelet.
	
	@class {public} pygowave.operations.OpManager
	"""
	
	# --- Event documentation ---
	"""
	Fired if an operation in this manager has been changed.
	@event onOperationChanged
	@param {int} index Index of the changed operation
	"""
	
	"""
	Fired if one or more operations are about to be removed.
	@event onBeforeOperationsRemoved
	@param {int} start Start index of the removal.
	@param {int} end End index of the removal.
	"""
	
	"""
	Fired if one or more operations have been removed.
	@event onAfterOperationsRemoved
	@param {int} start Start index of the removal.
	@param {int} end End index of the removal.
	"""
	
	"""
	Fired if one or more operations are about to be inserted.
	@event onBeforeOperationsInserted
	@param {int} start Start index of the insertion.
	@param {int} end End index of the insertion.
	"""
	
	"""
	Fired if one or more operations have been inserted.
	@event onAfterOperationsInserted
	@param {int} start Start index of the insertion.
	@param {int} end End index of the insertion.
	"""
	# ---------------------------
	
	def __init__(self, waveId, waveletId, contributorId):
		"""
		Initializes the op manager with a wave and wavelet ID.
		
		@constructor {public} initialize
		@param {String} waveId The ID of the wave
		@param {String} waveletId The ID of the wavelet
		@param {String} contributorId The ID of the contributor (= creator of the Delta)
		"""
		self.waveId = waveId
		self.waveletId = waveletId
		self.operations = Array()
		self.lockedBlips = Array()
		self.contributorId = contributorId

	def isEmpty(self):
		"""
		Return true if this manager is not holding operations.
		
		@function {public Boolean} isEmpty
		"""
		return len(self.operations) == 0

	def canFetch(self):
		"""
		Returns if the manager holds fetchable operations i.e. that are not
		locked.
		
		@function {public Boolean} canFetch
		"""
		for op in self.operations:
			if not self.lockedBlips.contains(op.blipId):
				return True
		return False

	def transform(self, input_op):
		"""
		Transform the input operation on behalf of the manager's operations
		list. This will simultaneously transform the operations list on behalf
		of the input operation.
		This method returns a list of applicable operations. This list may be
		empty or it may contain any number of new operations (according to
		results of deletion, modification and splitting; i.e. the input
		operation is not modified by itself).
		
		@function {public Operation[]} transform
		@param {Operation} input_op
		"""
		
		new_op = None
		op_lst = [input_op.clone()]
		
		# From The Zen of Python, by Tim Peters:
		# "Complex is better than complicated."
		
		i = 0
		while i < len(self.operations):
			myop = self.operations[i]
			j = 0
			while j < len(op_lst):
				op = op_lst[j]
				
				# Do not handle incompatible operations
				if not op.isCompatibleTo(myop): continue
				
				# Check all possible cases
				
				end = None
				if op.isDelete() and myop.isDelete():
					if op.index < myop.index:
						end = op.index + op.length()
						if end <= myop.index:
							myop.index -= op.length()
							self.fireEvent("operationChanged", i)
						elif end < myop.index + myop.length(): # and end > myop.index
							op.resize(myop.index - op.index)
							myop.resize(myop.length() - (end - myop.index))
							myop.index = op.index
							self.fireEvent("operationChanged", i)
						else: # end >= myop.index + myop.length()
							op.resize(op.length() - myop.length())
							self.removeOperation(i)
							i -= 1
							break
					else: # op.index >= myop.index
						end = myop.index + myop.length()
						if op.index >= end:
							op.index -= myop.length()
						elif op.index + op.length() <= end: # and op.index < end
							myop.resize(myop.length() - op.length())
							op_lst.pop(j)
							j -= 1
							if myop.isNull():
								self.removeOperation(i)
								i -= 1
								break
							else:
								self.fireEvent("operationChanged", i)
						else: # op.index + op.length() > end
							myop.resize(myop.length() - (end - op.index))
							self.fireEvent("operationChanged", i)
							op.resize(op.length() - (end - op.index))
							op.index = myop.index
				
				elif op.isDelete() and myop.isInsert():
					if op.index < myop.index:
						if op.index + op.length() <= myop.index:
							myop.index -= op.length()
							self.fireEvent("operationChanged", i)
						else: # op.index + op.length() > myop.index
							new_op = op.clone()
							op.resize(myop.index - op.index)
							new_op.resize(new_op.length() - op.length())
							op_lst.insert(j+1, new_op)
							myop.index -= op.length()
							self.fireEvent("operationChanged", i)
					else: # op.index >= myop.index
						op.index += myop.length()
				
				elif op.isInsert() and myop.isDelete():
					if op.index <= myop.index:
						myop.index += op.length()
						self.fireEvent("operationChanged", i)
					elif op.index >= myop.index + myop.length(): # op.index > myop.index
						op.index -= myop.length()
					else: # op.index < myop.index + myop.length()
						new_op = myop.clone()
						myop.resize(op.index - myop.index)
						self.fireEvent("operationChanged", i)
						new_op.resize(new_op.length() - myop.length())
						self.insertOperation(i+1, new_op)
						op.index = myop.index
				
				elif op.isInsert() and myop.isInsert():
					if op.index <= myop.index:
						myop.index += op.length()
						self.fireEvent("operationChanged", i)
					else: # op.index > myop.index
						op.index += myop.length()
				elif op.isChange() and myop.isDelete():
					if op.index > myop.index:
						if op.index <= myop.index + myop.length():
							op.index = myop.index
						else:
							op.index -= myop.length()
				elif op.isChange() and myop.isInsert():
					if op.index >= myop.index:
						op.index += myop.length()
				elif op.isDelete() and myop.isChange():
					if op.index < myop.index:
						if myop.index <= op.index + op.length():
							myop.index = op.index
							self.fireEvent("operationChanged", i)
						else:
							myop.index -= op.length()
							self.fireEvent("operationChanged", i)
				elif op.isInsert() and myop.isChange():
					if op.index <= myop.index:
						myop.index += op.length()
						self.fireEvent("operationChanged", i)
				elif (op.type == WAVELET_ADD_PARTICIPANT and myop.type == WAVELET_ADD_PARTICIPANT) \
					or (op.type == WAVELET_REMOVE_PARTICIPANT and myop.type == WAVELET_REMOVE_PARTICIPANT):
					if op.property == myop.property:
						self.removeOperation(i)
						i -= 1
						break
				elif op.type == BLIP_DELETE and op.blipId != "" and myop.blipId != "": # Kills all other Blip operations!
					# blipId was already checked by isCompatibleTo
					self.removeOperation(i)
					i -= 1
					break
				
				j += 1
				
			i += 1
		
		return op_lst
	
	def fetch(self):
		"""
		Returns the pending operations and removes them from this manager.
		
		@function {public Operation[]} fetch
		"""
		ops = Array()
		i = 0
		s = 0
		while i < len(self.operations):
			op = self.operations[i]
			if self.lockedBlips.contains(op.blipId):
				if i - s > 0:
					self.removeOperations(s, i-1)
					i -= s+1
				s = i+1
			else:
				ops.append(op)
			i += 1
		if i - s > 0:
			self.removeOperations(s, i-1)
		return ops
	
	def put(self, ops):
		"""
		Opposite of fetch. Inserts all given operations into this manager.
		
		@function {public} put
		@param {Operation[]} ops
		"""
		if len(ops) == 0:
			return
		start = len(self.operations)
		end = start + len(ops) - 1
		self.fireEvent("beforeOperationsInserted", [start, end])
		self.operations.extend(ops)
		self.fireEvent("afterOperationsInserted", [start, end])

	def serialize(self, fetch = False):
		"""
		Serialize this manager's operations into a list of dictionaries.
		Set fetch to true to also clear this manager.
		
		@function {public Object[]} serialize
		@param {optional Boolean} fetch
		"""
		if fetch:
			ops = self.fetch()
		else:
			ops = self.operations
		
		out = Array()
		
		for op in ops:
			out.append(op.serialize())
		
		return out
	
	def unserialize(self, serial_ops):
		"""
		Unserialize a list of dictionaries to operations and add them to this
		manager.
		
		@function {public} unserialize
		@param {Object[]} serial_ops
		"""
		
		ops = Array()
		
		for op in serial_ops:
			ops.append(Operation.unserialize(op))
		
		self.put(ops)

	def mergeInsert(self, newop):
		"""
		Inserts and probably merges an operation into the manager's
		operation list.
		
		@function {private} mergeInsert
		@param {Operation} newop
		"""
		
		# Element delta's can always merge with predecessors
		op = None
		i = 0
		if newop.type == DOCUMENT_ELEMENT_DELTA:
			for i in xrange(len(self.operations)):
				op = self.operations[i]
				if op.type == DOCUMENT_ELEMENT_DELTA and newop.property["id"] == op.property["id"]:
					op.property["delta"].update(newop.property["delta"])
					self.fireEvent("operationChanged", i)
					return
		
		# Others: Only merge with the last op (otherwise this may get a bit complicated)
		i = len(self.operations) - 1
		if i >= 0:
			op = self.operations[i]
			if newop.type == DOCUMENT_INSERT and op.type == DOCUMENT_INSERT:
				if newop.index >= op.index and newop.index <= op.index+op.length():
					op.insertString(newop.index-op.index, newop.property)
					self.fireEvent("operationChanged", i)
					return
			elif newop.type == DOCUMENT_DELETE and op.type == DOCUMENT_INSERT:
				if newop.index >= op.index and newop.index < op.index+op.length():
					remain = op.length() - (newop.index - op.index)
					if remain > newop.length():
						op.deleteString(newop.index - op.index, newop.length())
						newop.resize(0)
					else:
						op.deleteString(newop.index - op.index, remain)
						newop.resize(newop.length() - remain)
					if op.isNull():
						self.removeOperation(i)
						i -= 1
					else:
						self.fireEvent("operationChanged", i)
					if newop.isNull():
						return
				elif newop.index < op.index and newop.index+newop.length() > op.index:
					if newop.index+newop.length() >= op.index+op.length():
						newop.resize(newop.length() - op.length())
						self.removeOperation(i)
						i -= 1
					else:
						dlength = newop.index+newop.length() - op.index
						newop.resize(newop.length() - dlength)
						op.deleteString(0, dlength)
						self.fireEvent("operationChanged", i)
			elif newop.type == DOCUMENT_DELETE and op.type == DOCUMENT_DELETE:
				if newop.index == op.index: # Delete at start
					op.resize(op.length() + newop.length())
					self.fireEvent("operationChanged", i)
					return
				if newop.index == op.index-newop.length(): # Delete at end
					op.index -= newop.length()
					op.resize(op.length() + newop.length())
					self.fireEvent("operationChanged", i)
					return
			elif (newop.type == WAVELET_ADD_PARTICIPANT and op.type == WAVELET_ADD_PARTICIPANT) \
				or (newop.type == WAVELET_REMOVE_PARTICIPANT and op.type == WAVELET_REMOVE_PARTICIPANT):
				if newop.property == op.property:
					return
		
		# If we reach this the operation could not be merged, so add it.
		self.insertOperation(i+1, newop)

	def insertOperation(self, index, op):
		"""
		Inserts an operation at the specified index.
		Fires signals appropriately.
		
		@function {public} insertOperation
		@param {int} index Position in operations list
		@param {Operation} op Operation object to insert
		"""
		if index > len(self.operations) or index < 0:
			return
		self.fireEvent("beforeOperationsInserted", [index, index])
		self.operations.insert(index, op)
		self.fireEvent("afterOperationsInserted", [index, index])
	
	def removeOperation(self, index):
		"""
		Removes an operation at the specified index.
		Fires signals appropriately.
		
		@function {public} removeOperation
		@param {int} index Position in operations list
		"""
		if index < 0 or index >= len(self.operations):
			return
		self.fireEvent("beforeOperationsRemoved", [index, index])
		self.operations.pop(index)
		self.fireEvent("afterOperationsRemoved", [index, index])

	def removeOperations(self, start, end):
		"""
		Removes operations between and including the given start and end
		indexes. Fires signals appropriately.
		
		@function {public} removeOperations
		@param {int} start
		@param {int} end
		"""
		if start < 0 or end < 0 or start > end or start >= len(self.operations) or end >= len(self.operations):
			return
		self.fireEvent("beforeOperationsRemoved", [start, end])
		if start == 0 and end == len(self.operations)-1:
			self.operations = Array()
		else:
			for i in xrange(start, end+1):
				self.operations.pop(start)
		self.fireEvent("afterOperationsRemoved", [start, end])

	def updateBlipId(self, tempId, blipId):
		"""
		Updates the ID of operations on temporary Blips.
		
		@function {public} updateBlipId
		@param {String} tempId Old temporary ID
		@param {String} blipId New persistent ID
		"""
		i = 0
		while i < len(self.operations):
			op = self.operations[i]
			if op.blipId == tempId:
				op.blipId = blipId
				self.fireEvent("operationChanged", i)
			i += 1
	
	def lockBlipOps(self, blipId):
		"""
		Prevents the Operations on the Blip with the given ID from being
		handed over via fetch().
		
		@function {public} lockBlipOps
		@param blipId
		"""
		if not self.lockedBlips.contains(blipId):
			self.lockedBlips.push(blipId)
	
	def unlockBlipOps(self, blipId):
		"""
		Allows the Operations on the Blip with the given ID from being
		handed over via fetch().
		
		@function {public} unlockBlipOps
		@param blipId
		"""
		self.lockedBlips.erase(blipId)

	# --------------------------------------------------------------------------

	def documentInsert(self, blipId, index, content):
		"""
		Requests to insert content into a document at a specific location.
		
		@function {public} documentInsert
		@param {String} blipId The Blip id that this operation is applied to
		@param {int} index The position insert the content at in ths document
		@param {String} content The content to insert
		"""
		self.mergeInsert(Operation(
			DOCUMENT_INSERT,
			self.waveId, self.waveletId, blipId,
			index,
			content
		))
	
	def documentDelete(self, blipId, start, end):
		"""
		Requests to delete content in a given range.
		
		@function {public} documentDelete
		@param {String} blipId The Blip id that this operation is applied to
		@param {int} start Start of the range
		@param {int} end End of the range
		"""
		self.mergeInsert(Operation(
			DOCUMENT_DELETE,
			self.waveId, self.waveletId, blipId,
			start,
			end-start # = length
		))
	
	def documentElementInsert(self, blipId, index, type, properties):
		"""
		Requests to insert an element at the given position.
		
		@function {public} documentElementInsert
		@param {String} blipId The Blip id that this operation is applied to
		@param {int} index Position of the new element
		@param {String} type Element type
		@param {Object} properties Element properties
		"""
		self.mergeInsert(Operation(
			DOCUMENT_ELEMENT_INSERT,
			self.waveId, self.waveletId, blipId,
			index,
			{
				"type": type,
				"properties": properties
			}
		))

	def documentElementDelete(self, blipId, index):
		"""
		Requests to delete an element from the given position.
		
		@function {public} documentElementDelete
		@param {String} blipId The Blip id that this operation is applied to
		@param {int} index Position of the element to delete
		"""
		self.mergeInsert(Operation(
			DOCUMENT_ELEMENT_DELETE,
			self.waveId, self.waveletId, blipId,
			index,
			None
		))
	
	def documentElementDelta(self, blipId, index, delta):
		"""
		Requests to apply a delta to the element at the given position.
		
		@function {public} documentElementDelta
		@param {String} blipId The Blip id that this operation is applied to
		@param {int} index Position of the element
		@param {Object} delta Delta to apply to the element
		"""
		self.mergeInsert(Operation(
			DOCUMENT_ELEMENT_DELTA,
			self.waveId, self.waveletId, blipId,
			index,
			delta
		))

	def documentElementSetpref(self, blipId, index, key, value):
		"""
		Requests to set a UserPref of the element at the given position.
		
		@function {public} documentElementSetpref
		@param {String} blipId The Blip id that this operation is applied to
		@param {int} index Position of the element
		@param {Object} key Name of the UserPref
		@param {Object} value Value of the UserPref
		"""
		self.mergeInsert(Operation(
			DOCUMENT_ELEMENT_SETPREF,
			self.waveId, self.waveletId, blipId,
			index,
			{
				"key": key,
				"value": value
			}
		))
	
	def waveletAddParticipant(self, id):
		"""
		Requests to add a Participant to the Wavelet.
		
		@function {public} waveletAddParticipant
		@param {String} id ID of the Participant to add
		"""
		self.mergeInsert(Operation(
			WAVELET_ADD_PARTICIPANT,
			self.waveId, self.waveletId, "",
			-1,
			id
		))
	
	def waveletRemoveParticipant(self, id):
		"""
		Requests to remove a Participant to the Wavelet.
		
		@function {public} waveletRemoveParticipant
		@param {String} id ID of the Participant to remove
		"""
		self.mergeInsert(Operation(
			WAVELET_REMOVE_PARTICIPANT,
			self.waveId, self.waveletId, "",
			-1,
			id
		))
	
	def waveletAppendBlip(self, tempId):
		"""
		Requests to append a new Blip to the Wavelet.
		
		@function {public} waveletAppendBlip
		@param {String} tempId Temporary Blip ID
		"""
		self.mergeInsert(Operation(
			WAVELET_APPEND_BLIP,
			self.waveId, self.waveletId, "",
			-1,
			{
				"waveId": self.waveId,
				"waveletId": self.waveletId,
				"blipId": tempId
			}
		))
	
	def blipDelete(self, blipId):
		"""
		Requests to delete a Blip.
		
		@function {public} blipDelete
		@param {String} blipId The Blip id that this operation is applied to
		"""
		self.mergeInsert(Operation(
			BLIP_DELETE,
			self.waveId, self.waveletId, blipId
		))
	
	def blipCreateChild(self, blipId, tempId):
		"""
		Requests to create a clild Blip.
		
		@function {public} blipCreateChild
		@param {String} blipId The parent Blip
		@param {String} tempId Temporary Blip ID
		"""
		self.mergeInsert(Operation(
			BLIP_CREATE_CHILD,
			self.waveId, self.waveletId, blipId,
			-1,
			{
				"waveId": self.waveId,
				"waveletId": self.waveletId,
				"blipId": tempId
			}
		))