UNPKG

soocrate-core

Version:

this is the core of soocrate application

286 lines (245 loc) 7.74 kB
import VVwE from 'version-vector-with-exceptions' import { TextEvent } from './TextEvent' var debug = require('debug')( 'CRATE:Communication:TextManager:AntiEntropyManager' ) export class AntiEntropyManager extends TextEvent { constructor(opts) { const EventName = opts.EventName || 'Anti-Entropy' super({ EventName, ...opts }) this._antiEntropyPeriod = opts.AntiEntropyPeriod this._textManager = opts.TextManager this._communicationChannel = this._document._communication._behaviors_comm this.on('Request', msg => { this.receiveRequest(msg) }) this.on('Response', msg => { this.receiveResponse(msg) }) setTimeout(() => { this.sendAntiEntropyRequest() }, 1000) } start() { debug( 'AntiEntropyManager', 'start', 'Period', this._antiEntropyPeriod, this ) // this._communicationChannel.broadcast.startAntiEntropy(this._antiEntropyPeriod); this.startAntiEntropy(2000) } receive(msg) { this.emit(msg.type, { ...msg }) } async receiveRequest({ id, causality }) { const localVVwE = this._document._communication._data_comm.broadcast._causality.clone() const remoteVVwE = new VVwE(null).constructor.fromJSON(causality) // cast debug('receiveRequest', { antiEntropyPeriod: this._antiEntropyPeriod, id, remoteVVwE, localVVwE }) const missingLSEQIDs = this.getMissingLSEQIDsForVVWE(localVVwE, remoteVVwE) const elements = this.getElements(missingLSEQIDs) // #2 send back the found elements debug( 'Receive AntiEntropy And there are differences', id, remoteVVwE, localVVwE, elements ) await this.sendAntiEntropyResponse(id, localVVwE, elements) if (missingLSEQIDs.length > 0) { console.log('sendAction', 'Title', this._document.name) this.sendAction('Title', this._document.name, id) } const inverseMissingLSEQIds = this.getMissingLSEQIDsForVVWE( remoteVVwE, localVVwE ) if (inverseMissingLSEQIds.length > 0) { this.sendAntiEntropyRequest(id) } } getMissingLSEQIDsForVVWE(VVWE1, VVWE2) { let missingLSEQIDs = [] // #1 for each entry of our VVwE, look if the remote VVwE knows less VVWE1.vector.arr.forEach(VVWE1Entry => { const VVWE2EntryIndex = VVWE2.vector.indexOf(VVWE1Entry) let VVWE2Entry = null if (VVWE2EntryIndex > 0) { VVWE2Entry = VVWE2.vector.arr[VVWE2EntryIndex] } const missingLSEQIDsEntry = this.getMissingLSEQIDsForOneEntry( VVWE1Entry, VVWE2Entry ) Array.prototype.push.apply(missingLSEQIDs, missingLSEQIDsEntry) }) return missingLSEQIDs } getMissingLSEQIDsForOneEntry(localEntry, remoteEntry) { let start = 1 if (remoteEntry) { start = remoteEntry.v + 1 } let missingLSEQIDs = [] for (let j = start; j <= localEntry.v; ++j) { // #B check if not one of the local exceptions if (localEntry.x.indexOf(j) < 0) { missingLSEQIDs.push({ _e: localEntry.e, _c: j }) } } // #C handle the exceptions of the remote vector if (remoteEntry) { for (let j = 0; j < remoteEntry.x.length; ++j) { let except = remoteEntry.x[j] if (localEntry.x.indexOf(except) < 0 && except <= localEntry.v) { missingLSEQIDs.push({ _e: localEntry.e, _c: except }) } } } return missingLSEQIDs } /*! * \brief search a set of elements in our sequence and return them * \param toSearch the array of elements {_e, _c} to search * \returns an array of nodes */ getElements(toSearch) { const lseqNodes = this.getLSEQNodes() debug('getElements', { toSearch, lseqNodes }) let elements = [] lseqNodes.forEach(lseqNode => { if (this.isIdInSet({ id: lseqNode.t.s, clock: lseqNode.t.c, toSearch })) { elements.push(lseqNode) } }) return elements } getLSEQNodes() { let LSEQNodeArray = [] const root = this._sequence.root let preorder = node => { if (node.e && node.e != '') { LSEQNodeArray.push(node) } const children = node.children children.forEach(child => { preorder(child) }) } preorder(root) return LSEQNodeArray } getLSEQNodes() { let LSEQNodeArray = [] const root = this._sequence.root let preorder = node => { if (node.e && node.e != '') { LSEQNodeArray.push(node) } const children = node.children children.forEach(child => { preorder(child) }) } preorder(root) return LSEQNodeArray } isIdInSet({ id, clock, toSearch }) { for (let j = 0; j < toSearch.length; j++) { const { _e, _c } = toSearch[j] if (_e === id && _c === clock) { return true } } return false } receiveResponse({ elements, causalityAtReceipt }) { debug('receiveResponse', { elements, causalityAtReceipt }) // #1 considere each message in the response independantly let elems = [] elements.forEach(lseqNode => { const causalId = this.getCausalID(lseqNode) const msg = { id: causalId } const authorID = lseqNode.t.s.split('-')[0] const pair = { elem: lseqNode.e, id: lseqNode.e.id } // #2 only check if the message has not been received yet if (!this.haveBeenReceived(msg)) { // this._document.causality.incrementFrom(causalID) // this to prevent the caret movement in the case of anti-entropy elems.push({ pair, id: authorID, causalId, antientropy: true }) } }) this.Event('Insert', { pairs: elems, stream: true }) // #3 merge causality structures this._document._communication.causality.merge(causalityAtReceipt) } /** * We started Antientropy mechanism in order to retreive old missed files */ startAntiEntropy(delta = 2000) { this._intervalAntiEntropy = setInterval(() => { this.sendAntiEntropyRequest() }, delta) } sendAntiEntropyRequest(neighbourId = null) { let id = this._document._options.editingSessionID const msg = { type: 'Request', id, causality: this._document._communication._data_comm.broadcast._causality } if (!neighbourId) { this.sendLocalBroadcast(msg) } else { this.unicast(neighbourId, msg, false, this.getCausalId()) } debug('sendAntiEntropyRequest', { type: 'Request', id, causality: this._document.causality }) } /** * Send entropy response * @deprecated * @param {[type]} origin [description] * @param {[type]} causalityAtReceipt [description] * @param {[type]} elements [description] * @return {[type]} [description] */ async sendAntiEntropyResponse(origin, causalityAtReceipt, elements) { let id = this._document._options.editingSessionID // #1 metadata of the antientropy response await this.unicast(origin, { type: 'Response', id, causalityAtReceipt, elements }) debug('sendAntiEntropyResponse', { type: 'Response', id, causalityAtReceipt, elements }) } stopAntiEnropy() { if (this._intervalAntiEntropy) { clearInterval(this._intervalAntiEntropy) } } close() { this.stopAntiEnropy() } }