diff --git a/src/utils/Snapshot.js b/src/utils/Snapshot.js index 791b43fa..ccd923e8 100644 --- a/src/utils/Snapshot.js +++ b/src/utils/Snapshot.js @@ -3,7 +3,6 @@ import { isDeleted, createDeleteSetFromStructStore, getStateVector, - getItem, getItemCleanStart, iterateDeletedStructs, writeDeleteSet, @@ -12,22 +11,18 @@ import { readStateVector, createDeleteSet, createID, - ID, getState, - findIndexCleanStart, - AbstractStruct, - writeClientsStructs, findIndexSS, - readUpdateV2, - UpdateEncoderV2, UpdateDecoderV2, - AbstractDSDecoder, AbstractDSEncoder, DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line + UpdateEncoderV2, + DefaultDSEncoder, + applyUpdateV2, + AbstractDSDecoder, AbstractDSEncoder, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line } from '../internals.js' import * as map from 'lib0/map.js' import * as set from 'lib0/set.js' import * as decoding from 'lib0/decoding.js' import * as encoding from 'lib0/encoding.js' -import { DefaultDSEncoder } from './encoding.js' export class Snapshot { /** @@ -161,59 +156,47 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => { /** * @param {Doc} originDoc * @param {Snapshot} snapshot + * @param {Doc} [newDoc] Optionally, you may define the Yjs document that receives the data from originDoc * @return {Doc} */ -export const createDocFromSnapshot = (originDoc, snapshot) => { +export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) => { if (originDoc.gc) { // we should not try to restore a GC-ed document, because some of the restored items might have their content deleted throw new Error('originDoc must not be garbage collected') } const { sv, ds } = snapshot - const needState = new Map(sv) - let len = 0 - const tempStructs = [] - /** - * State Map - * @type any[] - */ - const itemsToIntegrate = [] - - /** - * @type Uint8Array - */ - let updateBuffer = new Uint8Array() + const encoder = new UpdateEncoderV2() originDoc.transact(transaction => { - const encoder = new UpdateEncoderV2() - - encoding.writeVarUint(encoder.restEncoder, sv.size) + let size = 0 + sv.forEach(clock => { + if (clock > 0) { + size++ + } + }) + encoding.writeVarUint(encoder.restEncoder, size) // splitting the structs before writing them to the encoder for (const [client, clock] of sv) { + if (clock === 0) { + continue + } if (clock < getState(originDoc.store, client)) { getItemCleanStart(transaction, createID(client, clock)) } - const structs = originDoc.store.clients.get(client) || [] const lastStructIndex = findIndexSS(structs, clock - 1) // write # encoded structs encoding.writeVarUint(encoder.restEncoder, lastStructIndex + 1) encoder.writeClient(client) - encoding.writeVarUint(encoder.restEncoder, structs[0].id.clock) - const firstStruct = structs[0] - firstStruct.write(encoder, 0) - for (let i = 1; i <= lastStructIndex; i++) { + // first clock written is 0 + encoding.writeVarUint(encoder.restEncoder, 0) + for (let i = 0; i <= lastStructIndex; i++) { structs[i].write(encoder, 0) } } - writeDeleteSet(encoder, ds) - - updateBuffer = encoder.toUint8Array() }) - const newDoc = new Doc() - - readUpdateV2(decoding.createDecoder(updateBuffer), newDoc, 'snapshot') - + applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot') return newDoc -} \ No newline at end of file +} diff --git a/tests/snapshot.tests.js b/tests/snapshot.tests.js index 0d2c49b0..b52cb67c 100644 --- a/tests/snapshot.tests.js +++ b/tests/snapshot.tests.js @@ -17,6 +17,26 @@ export const testBasicRestoreSnapshot = tc => { t.compare(doc.getArray('array').toArray(), ['hello', 'world']) } +/** + * @param {t.TestCase} tc + */ +export const testEmptyRestoreSnapshot = tc => { + const doc = new Doc({ gc: false }) + const snap = snapshot(doc) + snap.sv.set(9999, 0) + doc.getArray().insert(0, ['world']) + + const docRestored = createDocFromSnapshot(doc, snap) + + t.compare(docRestored.getArray().toArray(), []) + t.compare(doc.getArray().toArray(), ['world']) + + // now this snapshot reflects the latest state. It shoult still work. + const snap2 = snapshot(doc) + const docRestored2 = createDocFromSnapshot(doc, snap2) + t.compare(docRestored2.getArray().toArray(), ['world']) +} + /** * @param {t.TestCase} tc */ @@ -74,7 +94,6 @@ export const testRestoreLeftItem = tc => { t.compare(doc.getArray('array').toArray(), ['item0']) } - /** * @param {t.TestCase} tc */ @@ -91,7 +110,6 @@ export const testDeletedItemsBase = tc => { t.compare(doc.getArray('array').toArray(), ['item0']) } - /** * @param {t.TestCase} tc */ @@ -108,7 +126,6 @@ export const testDeletedItems2 = tc => { t.compare(doc.getArray('array').toArray(), ['item0', 'item1', 'item3']) } - /** * @param {t.TestCase} tc */ @@ -152,4 +169,3 @@ export const testDependentChanges = tc => { const docRestored1 = createDocFromSnapshot(array1.doc, snap) t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1']) } -