insomnia/packages/insomnia-app/app/sync/delta/diff.ts
Opender Singh d7b630f17b
Add formatting ESLint rules (#3425)
* add rules

* run lint:fix

* quick manual fix for lint errors

* indent fix

* add jsx prop multiline

Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
2021-05-27 14:00:32 -04:00

125 lines
2.9 KiB
TypeScript

import crypto from 'crypto';
interface InsertOperation {
type: 'INSERT';
content: string;
}
interface CopyOperation {
type: 'COPY';
start: number;
len: number;
}
export type Operation = InsertOperation | CopyOperation;
interface Block {
start: number;
len: number;
hash: string;
}
export function diff(source: string, target: string, blockSize: number): Operation[] {
const operations: Operation[] = [];
const sourceBlockMap = getBlockMap(source, blockSize);
// Iterate over source blocks in order and match them to target
let lastTargetMatch = 0;
for (let targetPosition = 0; targetPosition < target.length;) {
const targetBlock = getBlock(target, targetPosition, blockSize);
const sourceBlocks = sourceBlockMap[targetBlock.hash] || [];
// @ts-expect-error -- TSCONVERSION appears to be a genuine error
if (sourceBlocks.length === 0) {
targetPosition++;
continue;
}
// TODO: Try all blocks
const sourceBlock = sourceBlocks[0];
// Try to match as far as possible
let sourceIndex = sourceBlock.start + sourceBlock.len;
let targetIndex = targetBlock.start + targetBlock.len;
while (targetIndex < target.length && sourceIndex < source.length) {
if (source[sourceIndex] === target[targetIndex]) {
targetIndex++;
sourceIndex++;
} else {
break;
}
}
while (
source[sourceIndex++] === target[targetIndex] &&
targetIndex < target.length &&
sourceIndex < source.length
) {
targetIndex++;
}
sourceIndex--;
// Found unknown bytes, INSERT them
if (targetBlock.start > lastTargetMatch) {
operations.push({
type: 'INSERT',
content: target.slice(lastTargetMatch, targetBlock.start),
});
}
// Source block found in target
operations.push({
type: 'COPY',
start: sourceBlock.start,
len: sourceIndex - sourceBlock.start,
});
targetPosition = lastTargetMatch = targetIndex;
}
// Add the target suffix if there's still some left
if (lastTargetMatch < target.length) {
operations.push({
type: 'INSERT',
content: target.slice(lastTargetMatch),
});
}
return operations;
}
function getBlock(value: string, start: number, blockSize: number): Block {
if (start >= value.length) {
throw new Error('Invalid block index');
}
const blockSlice = value.slice(start, start + blockSize);
return {
start,
len: blockSlice.length,
hash: crypto.createHash('sha1').update(blockSlice).digest('hex'),
};
}
function getBlockMap(value: string, blockSize: number): Record<string, Block> {
const map = {};
for (let i = 0; i < value.length;) {
const block = getBlock(value, i, blockSize);
if (map[block.hash]) {
map[block.hash].push(block);
} else {
map[block.hash] = [block];
}
i += block.len;
}
return map;
}
export const __internal = {
getBlockMap,
};