private void HandleErrorOnUpdate(UpdateDto dto) { if (IsNotOwnUpdate(dto)) { _editor.ReloadDocument(dto.DocumentId); } }
public void UpdateRequest(string host, UpdateDto updateDto) { using (var cf = GetChannelFactory(host)) { cf.CreateChannel().UpdateRequest(updateDto); } }
private bool MergePendingUpdate(Document document, UpdateDto updateDto, Tuple <string, bool[]> resultAppliedGivenUpdate) { var everythingOk = true; var pendingUpdate = document.PendingUpdate; if (pendingUpdate != null) { //will the pending update be applied after the given update? if (MemberOfFirstUpdateIsNotOwnerAndHigherMember(pendingUpdate, updateDto)) { everythingOk = MergePendingUpdateAfterGivenUpdate(updateDto, pendingUpdate); } else if (MemberOfFirstUpdateIsOwnerOrLowerMember(pendingUpdate, updateDto)) { everythingOk = MergePendingUpdateBeforeGivenUpdate(document, updateDto, pendingUpdate, resultAppliedGivenUpdate); } } else { //no mergin needed, only update the editor _editor.UpdateText(updateDto.DocumentId, resultAppliedGivenUpdate.Item1); } return(everythingOk); }
private void MergeUpdate(Document document, UpdateDto updateDto) { var resultAppliedGivenUpdate = _diffMatchPatch.patch_apply(updateDto.Patch, document.Content); if (CheckResultIsValidOtherwiseReOpen(resultAppliedGivenUpdate, updateDto.DocumentId)) { if (MergePendingUpdate(document, updateDto, resultAppliedGivenUpdate)) { UpdateDocument(document, updateDto, resultAppliedGivenUpdate); } } //check whether we have an out of sync update which is based on the given update (so we could apply it as well) if (document.OutOfSyncUpdate != null && IsFirstPreviousOfSecond(updateDto, document.OutOfSyncUpdate)) { var outOfSynUpdate = document.OutOfSyncUpdate; document.OutOfSyncUpdate = null; MergeUpdate(document, outOfSynUpdate); } var outOfSyncAcknowledge = document.OutOfSyncAcknowledge; if (outOfSyncAcknowledge != null && IsFirstPreviousOfSecond(updateDto, document.PendingUpdate)) { ConfirmPendingUpdate(document, outOfSyncAcknowledge); document.OutOfSyncAcknowledge = null; } }
private bool MergePendingUpdateBeforeGivenUpdate(Document document, UpdateDto updateDto, UpdateDto pendingUpdate, Tuple <string, bool[]> resultAppliedGivenUpdate) { // merging happens as follows, first apply pending patch on document, then given update = new content // yet, since server has already applied given update we need to set pending's hash to the has of the update // and we need to recompute the patch of the pending patch in order that we get the same text when the ACK follows pendingUpdate.PreviousHash = updateDto.NewHash; var documentId = updateDto.DocumentId; var resultAppliedPendingUpdate = _diffMatchPatch.patch_apply(pendingUpdate.Patch, document.Content); bool everythingOk = CheckResultIsValidOtherwiseReOpen(resultAppliedPendingUpdate, documentId); if (everythingOk) { var resultAfterPatches = _diffMatchPatch.patch_apply(updateDto.Patch, resultAppliedPendingUpdate.Item1); everythingOk = CheckResultIsValidOtherwiseReOpen(resultAfterPatches, documentId); if (everythingOk) { //compute new patch for pendin gupdate pendingUpdate.Patch = _diffMatchPatch.patch_make(resultAppliedGivenUpdate.Item1, resultAfterPatches.Item1); // Merge screen and update -> screen is built up on pending update, thus create patch pending -> screen // then apply patch to new content var mergePatch = _diffMatchPatch.patch_make(resultAppliedPendingUpdate.Item1, _editor.GetText(documentId)); var resultMerge = _diffMatchPatch.patch_apply(mergePatch, resultAfterPatches.Item1); everythingOk = CheckResultIsValidOtherwiseReOpen(resultMerge, documentId); if (everythingOk) { _editor.UpdateText(documentId, resultMerge.Item1); } } } return(everythingOk); }
private void SendUpdateToDocumentOwner(Document document, UpdateDto dto) { try { _communication.UpdateRequest(document.OwnerHost, dto); } catch (EndpointNotFoundException) { _editor.ServerUnreachable(document.Id); } }
private void UpdateDocument(Document document, UpdateDto updateDto, Tuple <string, bool[]> resultAppliedGivenUpdate) { document.Content = resultAppliedGivenUpdate.Item1; document.CurrentRevisionId = updateDto.NewRevisionId; document.CurrentHash = GetHash(document.Content); if (!document.CurrentHash.SequenceEqual(updateDto.NewHash)) { //oho... should be the same, something went terribly wrong ReOpenDocument(document.Id); } }
private UpdateDto CreateUpdateDto(Document document, string content) { var updateDto = new UpdateDto { DocumentId = document.Id, PreviousRevisionId = document.CurrentRevisionId, PreviousHash = document.CurrentHash, Patch = _diffMatchPatch.patch_make(document.Content, content), MemberName = _memberName, MemberHost = _serverHost }; return(updateDto); }
private bool MergePendingUpdateAfterGivenUpdate(UpdateDto updateDto, UpdateDto pendingUpdate) { // it's enough to set the previous hash to the hash of the given update pendingUpdate.PreviousHash = updateDto.NewHash; // Merge screen and update var documentId = updateDto.DocumentId; var result = _diffMatchPatch.patch_apply(updateDto.Patch, _editor.GetText(documentId)); var everythingOk = CheckResultIsValidOtherwiseReOpen(result, documentId); if (everythingOk) { _editor.UpdateText(documentId, result.Item1); } return(everythingOk); }
private void ApplyUpdate(Document document, UpdateDto dto) { if (document.CurrentRevisionId == dto.PreviousRevisionId && document.CurrentHash.SequenceEqual(dto.PreviousHash)) { MergeUpdate(document, dto); } else if (document.OutOfSyncUpdate == null) { //update out of sync. Got an update which is not based on previous revision document.OutOfSyncUpdate = dto; } else { //too many out of sync updates, need to re-open the document ReOpenDocument(dto.DocumentId); } }
public void UpdateRequest(UpdateDto dto) { //do we know such a document? if (_documents.ContainsKey(dto.DocumentId)) { var document = _documents[dto.DocumentId]; //I am the owner/server? if (document.Owner == _memberName) { _editor.UpdateNumberOfEditors(document.Id, document.EditorCount); CreatePatchForUpdate(document, dto); } else { _editor.UpdateNumberOfEditors(document.Id, dto.EditorCount); ApplyUpdate(document, dto); } } }
private UpdateDto CreateUpdateDto(Document document, string content) { var updateDto = new UpdateDto { DocumentId = document.Id, PreviousRevisionId = document.CurrentRevisionId, PreviousHash = document.CurrentHash, Patch = _diffMatchPatch.patch_make(document.Content, content), MemberName = _memberName, MemberHost = _serverHost }; return updateDto; }
private bool MergePendingUpdate(Document document, UpdateDto updateDto, Tuple<string, bool[]> resultAppliedGivenUpdate) { var everythingOk = true; var pendingUpdate = document.PendingUpdate; if (pendingUpdate != null) { //will the pending update be applied after the given update? if (MemberOfFirstUpdateIsNotOwnerAndHigherMember(pendingUpdate, updateDto)) { everythingOk = MergePendingUpdateAfterGivenUpdate(updateDto, pendingUpdate); } else if (MemberOfFirstUpdateIsOwnerOrLowerMember(pendingUpdate, updateDto)) { everythingOk = MergePendingUpdateBeforeGivenUpdate(document, updateDto, pendingUpdate, resultAppliedGivenUpdate); } } else { //no mergin needed, only update the editor _editor.UpdateText(updateDto.DocumentId, resultAppliedGivenUpdate.Item1); } return everythingOk; }
private bool IsFirstPreviousOfSecond(UpdateDto lastUpdate, UpdateDto updateDto) { return lastUpdate.NewRevisionId == updateDto.PreviousRevisionId && lastUpdate.NewHash.SequenceEqual(updateDto.PreviousHash); }
private bool MergePendingUpdateAfterGivenUpdate(UpdateDto updateDto, UpdateDto pendingUpdate) { // it's enough to set the previous hash to the hash of the given update pendingUpdate.PreviousHash = updateDto.NewHash; // Merge screen and update var documentId = updateDto.DocumentId; var result = _diffMatchPatch.patch_apply(updateDto.Patch, _editor.GetText(documentId)); var everythingOk = CheckResultIsValidOtherwiseReOpen(result, documentId); if (everythingOk) { _editor.UpdateText(documentId, result.Item1); } return everythingOk; }
private bool IsNotOwnUpdate(UpdateDto updateDto) { return updateDto.MemberName != _memberName; }
private bool MemberOfFirstUpdateIsOwnerOrLowerMember(UpdateDto firstUpdateDto, UpdateDto secondUpdateDto) { return(!IsNotOwnUpdate(firstUpdateDto) || string.Compare(firstUpdateDto.MemberName, secondUpdateDto.MemberName) < 0); }
private bool MemberOfFirstUpdateIsNotOwnerAndHigherMember(UpdateDto firstUpdateDto, UpdateDto secondUpdateDto) { return(IsNotOwnUpdate(firstUpdateDto) && string.Compare(firstUpdateDto.MemberName, secondUpdateDto.MemberName) > 0); }
private bool MemberOfFirstUpdateIsOwnerOrLowerMember(UpdateDto firstUpdateDto, UpdateDto secondUpdateDto) { return !IsNotOwnUpdate(firstUpdateDto) || string.Compare(firstUpdateDto.MemberName, secondUpdateDto.MemberName) < 0; }
private bool IsNotOwnUpdate(UpdateDto updateDto) { return(updateDto.MemberName != _memberName); }
private bool MergePendingUpdateBeforeGivenUpdate(Document document, UpdateDto updateDto, UpdateDto pendingUpdate, Tuple<string, bool[]> resultAppliedGivenUpdate) { // merging happens as follows, first apply pending patch on document, then given update = new content // yet, since server has already applied given update we need to set pending's hash to the has of the update // and we need to recompute the patch of the pending patch in order that we get the same text when the ACK follows pendingUpdate.PreviousHash = updateDto.NewHash; var documentId = updateDto.DocumentId; var resultAppliedPendingUpdate = _diffMatchPatch.patch_apply(pendingUpdate.Patch, document.Content); bool everythingOk = CheckResultIsValidOtherwiseReOpen(resultAppliedPendingUpdate, documentId); if (everythingOk) { var resultAfterPatches = _diffMatchPatch.patch_apply(updateDto.Patch, resultAppliedPendingUpdate.Item1); everythingOk = CheckResultIsValidOtherwiseReOpen(resultAfterPatches, documentId); if (everythingOk) { //compute new patch for pendin gupdate pendingUpdate.Patch = _diffMatchPatch.patch_make(resultAppliedGivenUpdate.Item1, resultAfterPatches.Item1); // Merge screen and update -> screen is built up on pending update, thus create patch pending -> screen // then apply patch to new content var mergePatch = _diffMatchPatch.patch_make(resultAppliedPendingUpdate.Item1, _editor.GetText(documentId)); var resultMerge = _diffMatchPatch.patch_apply(mergePatch, resultAfterPatches.Item1); everythingOk = CheckResultIsValidOtherwiseReOpen(resultMerge, documentId); if (everythingOk) { _editor.UpdateText(documentId, resultMerge.Item1); } } } return everythingOk; }
private void UpdateDocument(Document document, UpdateDto updateDto, Tuple<string, bool[]> resultAppliedGivenUpdate) { document.Content = resultAppliedGivenUpdate.Item1; document.CurrentRevisionId = updateDto.NewRevisionId; document.CurrentHash = GetHash(document.Content); if (!document.CurrentHash.SequenceEqual(updateDto.NewHash)) { //oho... should be the same, something went terribly wrong ReOpenDocument(document.Id); } }
private bool IsFirstPreviousOfSecond(UpdateDto lastUpdate, UpdateDto updateDto) { return(lastUpdate.NewRevisionId == updateDto.PreviousRevisionId && lastUpdate.NewHash.SequenceEqual(updateDto.PreviousHash)); }
private bool MemberOfFirstUpdateIsNotOwnerAndHigherMember(UpdateDto firstUpdateDto, UpdateDto secondUpdateDto) { return IsNotOwnUpdate(firstUpdateDto) && string.Compare(firstUpdateDto.MemberName, secondUpdateDto.MemberName) > 0; }
private void CreatePatchForUpdate(Document document, UpdateDto updateDto) { var currentRevision = document.GetCurrentRevision(); var lastUpdate = currentRevision.UpdateDto; //non existing revision - used as null object var secondLastUpdate = new UpdateDto {NewRevisionId = -1}; if (document.CurrentRevisionId > FIRST_VALID_REVISON_ID) { secondLastUpdate=document.GetRevision(document.CurrentRevisionId - 1).UpdateDto; } bool creationSucessfull = false; //update is either based on current version //or on previous version where // - the member which initialised the previous version was the owner itself // - or a member with a lower member name (and thus the given update will be applied afterwards) //) if (IsFirstPreviousOfSecond(lastUpdate, updateDto) || (IsFirstPreviousOfSecond(secondLastUpdate, updateDto) && MemberOfFirstUpdateIsOwnerOrLowerMember(lastUpdate, updateDto) ) ) { var result = _diffMatchPatch.patch_apply(updateDto.Patch, document.Content); if (result.Item2.All(x => x)) { document.Content = result.Item1; creationSucessfull = true; }else { HandleErrorOnUpdate(updateDto); } } else { var revision = document.GetRevision(updateDto.PreviousRevisionId); if (revision.Id + SUPPORTED_NUM_OF_REACTIVE_UPDATES >= currentRevision.Id) { var nextRevision = document.GetRevision(revision.Id + 1); //move to next revision as long as while ( nextRevision.UpdateDto.PreviousHash.SequenceEqual(updateDto.PreviousHash) && MemberOfFirstUpdateIsNotOwnerAndHigherMember(updateDto, nextRevision.UpdateDto) && nextRevision.Id < currentRevision.Id) { revision = nextRevision; nextRevision = document.GetRevision(nextRevision.Id + 1); } var content = revision.Content; var tmpRevision = revision; var patch = updateDto.Patch; //apply all patches on top of the found revision while (tmpRevision.Id <= currentRevision.Id) { var result = _diffMatchPatch.patch_apply(patch, content); if (result.Item2.All(x => x)) { content = result.Item1; if (tmpRevision.Id == currentRevision.Id) { break; } tmpRevision = document.GetRevision(tmpRevision.Id + 1); patch = tmpRevision.UpdateDto.Patch; } else { HandleErrorOnUpdate(updateDto); } } document.Content = content; updateDto.Patch = _diffMatchPatch.patch_make(currentRevision.Content, content); creationSucessfull = true; } } if (creationSucessfull) { document.CurrentRevisionId = currentRevision.Id + 1; document.CurrentHash = GetHash(document.Content); updateDto.NewRevisionId = document.CurrentRevisionId; updateDto.NewHash = document.CurrentHash; document.AddRevision(new Revision { Id = document.CurrentRevisionId, Content = document.Content, UpdateDto = updateDto }); if (IsNotOwnUpdate(updateDto)) { _editor.UpdateText(updateDto.DocumentId, document.Content); var acknowledgeDto = new AcknowledgeDto { PreviousRevisionId = updateDto.PreviousRevisionId, PreviousHash = updateDto.PreviousHash, NewRevisionId = updateDto.NewRevisionId, NewHash = updateDto.NewHash, DocumentId = document.Id }; _communication.AckRequest(updateDto.MemberHost, acknowledgeDto); } var newUpdateDto = new UpdateDto { DocumentId = document.Id, MemberName = updateDto.MemberName, MemberHost = _serverHost, PreviousRevisionId = updateDto.PreviousRevisionId, PreviousHash = updateDto.PreviousHash, NewRevisionId = updateDto.NewRevisionId, NewHash = updateDto.NewHash, Patch = updateDto.Patch, EditorCount = document.EditorCount }; foreach (var editorHost in document.Editors().Values) { if (updateDto.MemberHost != editorHost) { try { _communication.UpdateRequest(editorHost, newUpdateDto); } catch (EndpointNotFoundException) { document.Editors().Remove(editorHost); } } } } else if (IsNotOwnUpdate(updateDto)) { HandleErrorOnUpdate(updateDto); } }
private void CreatePatchForUpdate(Document document, UpdateDto updateDto) { var currentRevision = document.GetCurrentRevision(); var lastUpdate = currentRevision.UpdateDto; //non existing revision - used as null object var secondLastUpdate = new UpdateDto { NewRevisionId = -1 }; if (document.CurrentRevisionId > FIRST_VALID_REVISON_ID) { secondLastUpdate = document.GetRevision(document.CurrentRevisionId - 1).UpdateDto; } bool creationSucessfull = false; //update is either based on current version //or on previous version where // - the member which initialised the previous version was the owner itself // - or a member with a lower member name (and thus the given update will be applied afterwards) //) if (IsFirstPreviousOfSecond(lastUpdate, updateDto) || (IsFirstPreviousOfSecond(secondLastUpdate, updateDto) && MemberOfFirstUpdateIsOwnerOrLowerMember(lastUpdate, updateDto) ) ) { var result = _diffMatchPatch.patch_apply(updateDto.Patch, document.Content); if (result.Item2.All(x => x)) { document.Content = result.Item1; creationSucessfull = true; } else { HandleErrorOnUpdate(updateDto); } } else { var revision = document.GetRevision(updateDto.PreviousRevisionId); if (revision.Id + SUPPORTED_NUM_OF_REACTIVE_UPDATES >= currentRevision.Id) { var nextRevision = document.GetRevision(revision.Id + 1); //move to next revision as long as while ( nextRevision.UpdateDto.PreviousHash.SequenceEqual(updateDto.PreviousHash) && MemberOfFirstUpdateIsNotOwnerAndHigherMember(updateDto, nextRevision.UpdateDto) && nextRevision.Id < currentRevision.Id) { revision = nextRevision; nextRevision = document.GetRevision(nextRevision.Id + 1); } var content = revision.Content; var tmpRevision = revision; var patch = updateDto.Patch; //apply all patches on top of the found revision while (tmpRevision.Id <= currentRevision.Id) { var result = _diffMatchPatch.patch_apply(patch, content); if (result.Item2.All(x => x)) { content = result.Item1; if (tmpRevision.Id == currentRevision.Id) { break; } tmpRevision = document.GetRevision(tmpRevision.Id + 1); patch = tmpRevision.UpdateDto.Patch; } else { HandleErrorOnUpdate(updateDto); } } document.Content = content; updateDto.Patch = _diffMatchPatch.patch_make(currentRevision.Content, content); creationSucessfull = true; } } if (creationSucessfull) { document.CurrentRevisionId = currentRevision.Id + 1; document.CurrentHash = GetHash(document.Content); updateDto.NewRevisionId = document.CurrentRevisionId; updateDto.NewHash = document.CurrentHash; document.AddRevision(new Revision { Id = document.CurrentRevisionId, Content = document.Content, UpdateDto = updateDto }); if (IsNotOwnUpdate(updateDto)) { _editor.UpdateText(updateDto.DocumentId, document.Content); var acknowledgeDto = new AcknowledgeDto { PreviousRevisionId = updateDto.PreviousRevisionId, PreviousHash = updateDto.PreviousHash, NewRevisionId = updateDto.NewRevisionId, NewHash = updateDto.NewHash, DocumentId = document.Id }; _communication.AckRequest(updateDto.MemberHost, acknowledgeDto); } var newUpdateDto = new UpdateDto { DocumentId = document.Id, MemberName = updateDto.MemberName, MemberHost = _serverHost, PreviousRevisionId = updateDto.PreviousRevisionId, PreviousHash = updateDto.PreviousHash, NewRevisionId = updateDto.NewRevisionId, NewHash = updateDto.NewHash, Patch = updateDto.Patch, EditorCount = document.EditorCount }; foreach (var editorHost in document.Editors().Values) { if (updateDto.MemberHost != editorHost) { try { _communication.UpdateRequest(editorHost, newUpdateDto); } catch (EndpointNotFoundException) { document.Editors().Remove(editorHost); } } } } else if (IsNotOwnUpdate(updateDto)) { HandleErrorOnUpdate(updateDto); } }