UNPKG

diffusion

Version:

Diffusion JavaScript client

411 lines (325 loc) 12 kB
var _implements = require('util/interface')._implements; var identity = require('util/function').identity; var BufferOutputStream = require('io/buffer-output-stream'); var BinaryDeltaTypeImpl = require('data/binary/binary-delta-type-impl'); var BytesImpl = require('data/bytes-impl'); var RecordV2Type = require('../../../data/record/record'); var BINARY_DELTA_TYPE = new BinaryDeltaTypeImpl(RecordV2Impl, from, identity); var RECORD_PARSER = require('data/record/recordv2-parser'); var RecordModelImpl = require('data/record/model/record-model-impl'); var RecordV2Delta = require('data/record/recordv2-delta-impl'); var utils = require('data/record/recordv2-utils'); function from(value) { if (value instanceof RecordV2Impl) { return value; } else { return new RecordV2Impl(value); } } function RecordV2Impl(buffer, offset, length) { // eslint-disable-next-line no-underscore-dangle BytesImpl.__constructor.call(this, buffer, offset, length); var self = this; this.apply = function(delta) { return BINARY_DELTA_TYPE.apply(self, delta); }; this.toString = function() { return JSON.stringify(self.asRecords()); }; this.asRecords = function() { return RECORD_PARSER.parse(self.$buffer, self.$offset, self.$length); }; this.asFields = function() { return self.asRecords().reduce(function(fields, record) { return fields.concat(record); }, []); }; this.asModel = function(schema) { return new RecordModelImpl(RecordV2Impl, schema, self.asRecords(), false); }; this.asValidatedModel = function(schema) { return new RecordModelImpl(RecordV2Impl, schema, self.asRecords(), true); }; this.binaryDiff = function(original) { return BINARY_DELTA_TYPE.diff(original, self); }; this.diff = function(original) { var oldBytes = original.$buffer; var oldOffset = original.$offset; var oldLength = original.$length; var newBytes = self.$buffer; var newOffset = self.$offset; var newLength = self.$length; if (newLength === 0) { if (oldLength === 0) { return RecordV2Delta.NO_CHANGE; } return deltaForEmptyNewValue(); } else if (utils.isSingleEmptyRecord(newBytes, newOffset, newLength)) { if (utils.isSingleEmptyRecord(oldBytes, oldOffset, oldLength)) { return RecordV2Delta.NO_CHANGE; } return deltaForSingleEmptyRecordNewValue(utils.recordCount(oldBytes, oldOffset, oldLength)); } if (oldLength === 0) { return deltaForAllNewRecords(newBytes, newOffset, newLength); } else if (utils.isSingleEmptyRecord(oldBytes, oldOffset, oldLength)) { return deltaForOldSingleEmptyRecord(newBytes, newOffset, newLength); } var result = diffRecords( oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength); if (result.hasChanges()) { var delta = result.getDelta(); return new RecordV2Delta( delta, 0, delta.length, result.getRecordChanges(), result.getFieldChanges()); } else { return RecordV2Delta.NO_CHANGE; } }; } function diffRecords( oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength) { var result = new DiffResult(); var oldCount = utils.recordCount(oldBytes, oldOffset, oldLength); var newCount = utils.recordCount(newBytes, newOffset, newLength); var same = Math.min(oldCount, newCount); var oldStart = oldOffset; var oldEnd = oldOffset; var newStart = newOffset; var newEnd = newOffset; var delimiter = false; for (var i = 0; i < same; ++i) { oldEnd = utils.findDelimiter(oldBytes, oldEnd, oldOffset + oldLength, utils.RECORD_DELIMITER); newEnd = utils.findDelimiter(newBytes, newEnd, newOffset + newLength, utils.RECORD_DELIMITER); if (delimiter) { result.write(utils.RECORD_DELIMITER); } else { delimiter = true; } diffRecord( i, oldBytes, oldStart, oldEnd - oldStart, newBytes, newStart, newEnd - newStart, result); oldStart = ++oldEnd; newStart = ++newEnd; } if (newCount !== oldCount) { if (newCount > oldCount) { result.write(newBytes, newStart - 1, newOffset + newLength - newStart + 1); } result.setRecordsChanged(newCount > oldCount, same); } return result; } function diffRecord( recordIndex, oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result) { if (newLength === 0) { if (oldLength !== 0) { result.setFieldsRemoved(recordIndex, 0); } return; } if (utils.recordIsSingleEmptyField(newBytes, newOffset, newLength)) { diffWhenNewIsSingleEmptyField(recordIndex, oldBytes, oldOffset, oldLength, result); return; } if (oldLength === 0) { result.write(newBytes, newOffset, newLength); result.setFieldsAdded(recordIndex, 0); return; } if (utils.recordIsSingleEmptyField(oldBytes, oldOffset, oldLength)) { diffWhenOldIsSingleEmptyField(recordIndex, newBytes, newOffset, newLength, result); return; } diffRecordFields(recordIndex, oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result); } function diffWhenNewIsSingleEmptyField(recordIndex, oldBytes, oldOffset, oldLength, result) { if (utils.recordIsSingleEmptyField(oldBytes, oldOffset, oldLength)) { result.write(utils.FIELD_MU); } else { var oldCount = utils.fieldCount(oldBytes, oldOffset, oldLength); if (oldCount > 1) { result.setFieldsRemoved(recordIndex, 1); } result.write(utils.EMPTY_FIELD); result.setChanged(); } } function diffWhenOldIsSingleEmptyField(recordIndex, newBytes, newOffset, newLength, result) { var b = newBytes[newOffset]; if (b === utils.EMPTY_FIELD) { result.write(newBytes, newOffset + 1, newLength - 1); } else { result.write(newBytes, newOffset, newLength); if (b !== utils.FIELD_DELIMITER) { result.setChanged(); } } if (utils.fieldCount(newBytes, newOffset, newLength) > 1) { result.setFieldsAdded(recordIndex, 1); } } function diffRecordFields(recordIndex, oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result) { var oldCount = utils.fieldCount(oldBytes, oldOffset, oldLength); var newCount = utils.fieldCount(newBytes, newOffset, newLength); var same = Math.min(oldCount, newCount); var oldStart = oldOffset; var oldEnd = oldOffset; var newStart = newOffset; var newEnd = newOffset; var startSize = result.size(); var delimiter = false; for (var i = 0; i < same; ++i) { oldEnd = utils.findDelimiter(oldBytes, oldEnd, oldOffset + oldLength, utils.FIELD_DELIMITER); newEnd = utils.findDelimiter(newBytes, newEnd, newOffset + newLength, utils.FIELD_DELIMITER); if (delimiter) { result.write(utils.FIELD_DELIMITER); } else { delimiter = true; } diffField( oldBytes, oldStart, oldEnd - oldStart, newBytes, newStart, newEnd - newStart, result); oldStart = ++oldEnd; newStart = ++newEnd; } if (newCount !== oldCount) { if (newCount > oldCount) { result.write(newBytes, newStart - 1, newOffset + newLength - newStart + 1); result.setFieldsAdded(recordIndex, same); } else { result.setFieldsRemoved(recordIndex, same); } } if (newCount === 1 && startSize === result.size()) { result.write(utils.FIELD_MU); } } function diffField(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result) { if (fieldsAreEqual(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength)) { return; } if (newLength === 0) { result.write(utils.EMPTY_FIELD); } else { result.write(newBytes, newOffset, newLength); } result.setChanged(); } function fieldsAreEqual(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength) { if (oldLength === newLength) { if (oldLength === 0 || fieldEquals(oldBytes, oldOffset, newBytes, newOffset, oldLength)) { return true; } } else if (oldLength === 1 && oldBytes[oldOffset] === utils.EMPTY_FIELD && newLength === 0) { return true; } else if (newLength === 1 && newBytes[newOffset] === utils.EMPTY_FIELD && oldLength === 0) { return true; } return false; } function fieldEquals(oldBytes, oldOffset, newBytes, newOffset, length) { for (var i = 0; i < length; ++i) { if (oldBytes[oldOffset + i] !== newBytes[newOffset + i]) { return false; } } return true; } function deltaForEmptyNewValue() { return new RecordV2Delta(new Buffer([]), 0, 0, [false, 0], {}); } function deltaForSingleEmptyRecordNewValue(oldRecordCount) { switch (oldRecordCount) { case 0 : return new RecordV2Delta(new Buffer([utils.RECORD_MU]), 0, 1, [true, 0], {}); case 1 : return new RecordV2Delta(new Buffer([utils.RECORD_MU]), 0, 1, null, { 0 : [false, 0] }); default : return new RecordV2Delta(new Buffer([utils.RECORD_MU]), 0, 1, [false, 1], { 0 : [false, 0] }); } } function deltaForAllNewRecords(newBytes, newOffset, newLength) { return new RecordV2Delta(newBytes, newOffset, newLength, [true, 0], {}); } /* * Return a delta for when the old value is a single empty record. * This assumes that the new record is neither empty nor a single empty * record. */ function deltaForOldSingleEmptyRecord(newBytes, newOffset, newLength) { var newRecordCount = utils.recordCount(newBytes, newOffset, newLength); if (newRecordCount === 1) { // Single record so fields added to first return new RecordV2Delta( newBytes, newOffset, newLength, null, { 0: [true, 0] }); } else { // Records added return new RecordV2Delta( newBytes, newOffset, newLength, [true, 1], newBytes[0] === utils.RECORD_DELIMITER ? {} : { 0: [true, 0] }); } } function DiffResult() { var delta = new BufferOutputStream(); var hasChanges = false; var recordChanges = null; var fieldChanges = {}; this.write = function(b, o, l) { if (o !== undefined && l !== undefined) { delta.writeMany(b, o, l); } else { delta.write(b); } }; this.getDelta = function() { return delta.getBuffer(); }; this.size = function() { return delta.count; }; this.hasChanges = function() { return hasChanges; }; this.setChanged = function() { hasChanges = true; }; this.setRecordsChanged = function(added, index) { recordChanges = [added, index]; hasChanges = true; }; this.getRecordChanges = function() { return recordChanges; }; this.setFieldsAdded = function(recordIndex, fieldIndex) { fieldChanges[recordIndex] = [true, fieldIndex]; hasChanges = true; }; this.setFieldsRemoved = function(recordIndex, fieldIndex) { fieldChanges[recordIndex] = [false, fieldIndex]; hasChanges = true; }; this.getFieldChanges = function() { return fieldChanges; }; } module.exports = _implements(RecordV2Type, RecordV2Impl); module.exports.from = from; module.exports.toString = function() { return "RecordV2Impl"; };