public static void WriteSyncStep2(Stream stream, YDoc doc, byte[] encodedStateVector) { stream.WriteVarUint(MessageYjsSyncStep2); var update = doc.EncodeStateAsUpdateV2(encodedStateVector); stream.WriteVarUint8Array(update); }
internal ContentDoc(YDoc doc) { if (doc._item != null) { throw new Exception("This document was already integrated as a sub-document. You should create a second instance instead with the same guid."); } Doc = doc; Opts = new YDocOptions(); if (!doc.Gc) { Opts.Gc = false; } if (doc.AutoLoad) { Opts.AutoLoad = true; } if (doc.Meta != null) { Opts.Meta = doc.Meta; } }
public void TestRestoreSnapshotWithSubType() { var doc = new YDoc(new YDocOptions { Gc = false }); doc.GetArray("array").Insert(0, new[] { new YMap() }); var subMap = doc.GetArray("array").Get(0) as YMap; subMap.Set("key1", "value1"); var snap = doc.CreateSnapshot(); subMap.Set("key2", "value2"); var docRestored = snap.RestoreDocument(doc); var restoredSubMap = docRestored.GetArray("array").Get(0) as YMap; subMap = doc.GetArray("array").Get(0) as YMap; Assert.AreEqual(1, restoredSubMap.Count); Assert.AreEqual("value1", restoredSubMap.Get("key1")); Assert.AreEqual(2, subMap.Count); Assert.AreEqual("value1", subMap.Get("key1")); Assert.AreEqual("value2", subMap.Get("key2")); }
public static void WriteSyncStep1(Stream stream, YDoc doc) { stream.WriteVarUint(MessageYjsSyncStep1); var sv = doc.EncodeStateVectorV2(); stream.WriteVarUint8Array(sv); }
public YDoc RestoreDocument(YDoc originDoc, YDocOptions opts = null) { if (originDoc.Gc) { // We should try to restore a GC-ed document, because some of the restored items might have their content deleted. throw new Exception("originDoc must not be garbage collected"); } using var encoder = new UpdateEncoderV2(); originDoc.Transact(tr => { int size = StateVector.Count(kvp => kvp.Value /* clock */ > 0); encoder.RestWriter.WriteVarUint((uint)size); // Splitting the structs before writing them to the encoder. foreach (var kvp in StateVector) { int client = kvp.Key; int clock = kvp.Value; if (clock == 0) { continue; } if (clock < originDoc.Store.GetState(client)) { tr.Doc.Store.GetItemCleanStart(tr, new ID(client, clock)); } var structs = originDoc.Store.Clients[client]; var lastStructIndex = StructStore.FindIndexSS(structs, clock - 1); // Write # encoded structs. encoder.RestWriter.WriteVarUint((uint)(lastStructIndex + 1)); encoder.WriteClient(client); // First clock written is 0. encoder.RestWriter.WriteVarUint(0); for (int i = 0; i <= lastStructIndex; i++) { structs[i].Write(encoder, 0); } } DeleteSet.Write(encoder); }); var newDoc = new YDoc(opts ?? originDoc.CloneOptionsWithNewGuid()); newDoc.ApplyUpdateV2(encoder.ToArray(), transactionOrigin: "snapshot"); return(newDoc); }
public void TestYMapEventExceptionsShouldCompleteTransaction() { var doc = new YDoc(); var map = doc.GetMap("map"); bool updateCalled = false; bool throwingObserverCalled = false; bool throwingDeepObserverCalled = false; doc.UpdateV2 += (s, e) => { updateCalled = true; }; void throwingObserver(object sender, YEventArgs args) { throwingObserverCalled = true; throw new Exception("Failure"); } void throwingDeepObserver(object sender, YDeepEventArgs args) { throwingDeepObserverCalled = true; throw new Exception("Deep failure"); } map.EventHandler += throwingObserver; map.DeepEventHandler += throwingDeepObserver; Assert.ThrowsException <Exception>(() => { map.Set("y", "2"); }); Assert.IsTrue(updateCalled); Assert.IsTrue(throwingObserverCalled); Assert.IsTrue(throwingDeepObserverCalled); // Check if it works again. updateCalled = false; throwingObserverCalled = false; throwingDeepObserverCalled = false; Assert.ThrowsException <Exception>(() => { map.Set("z", "3"); }); Assert.IsTrue(updateCalled); Assert.IsTrue(throwingObserverCalled); Assert.IsTrue(throwingDeepObserverCalled); Assert.AreEqual("3", map.Get("z")); }
internal override void Integrate(YDoc doc, Item item) { base.Integrate(doc, item); foreach (var kvp in _prelimContent) { Set(kvp.Key, kvp.Value); } _prelimContent = null; }
public void TestGetTypeEmptyId() { var doc1 = new YDoc(); doc1.GetText(string.Empty).Insert(0, "h"); doc1.GetText().Insert(1, "i"); var doc2 = new YDoc(); doc2.ApplyUpdateV2(doc1.EncodeStateAsUpdateV2()); Assert.AreEqual("hi", doc2.GetText().ToString()); Assert.AreEqual("hi", doc2.GetText(string.Empty).ToString()); }
public void TestClientIdDuplicateChange() { var doc1 = new YDoc(); doc1.ClientId = 0; var doc2 = new YDoc(); doc2.ClientId = 0; Assert.AreEqual(doc1.ClientId, doc2.ClientId); doc1.GetArray("a").Insert(0, new object[] { 1, 2 }); doc2.ApplyUpdateV2(doc1.EncodeStateAsUpdateV2()); Assert.AreNotEqual(doc1.ClientId, doc2.ClientId); }
public void TestBasicUpdate() { var doc1 = new YDoc(); var doc2 = new YDoc(); var content = new List <object> { "hi" }; doc1.GetArray("array").Insert(0, content); var update = doc1.EncodeStateAsUpdateV2(); doc2.ApplyUpdateV2(update); CollectionAssert.AreEqual(content, (ICollection)doc2.GetArray("array").ToArray()); }
// TODO: [alekseyk] Set default parameters (not for the Func<>), or create options, like YDocOptions. public UndoManager(IList <AbstractType> typeScopes, int captureTimeout, Func <Item, bool> deleteFilter, ISet <object> trackedOrigins) { _scope = typeScopes; _deleteFilter = deleteFilter ?? (_ => true); _trackedOrigins = trackedOrigins ?? new HashSet <object>(); _trackedOrigins.Add(this); _undoStack = new Stack <StackItem>(); _redoStack = new Stack <StackItem>(); _undoing = false; _redoing = false; _doc = typeScopes[0].Doc; _lastChange = DateTime.MinValue; _captureTimeout = captureTimeout; _doc.AfterTransaction += OnAfterTransaction; }
internal Transaction(YDoc doc, object origin, bool local) { Doc = doc; DeleteSet = new DeleteSet(); BeforeState = Doc.Store.GetStateVector(); AfterState = new Dictionary <int, int>(); Changed = new Dictionary <AbstractType, ISet <string> >(); ChangedParentTypes = new Dictionary <AbstractType, IList <YEvent> >(); _mergeStructs = new List <AbstractStruct>(); Origin = origin; Meta = new Dictionary <string, object>(); Local = local; SubdocsAdded = new HashSet <YDoc>(); SubdocsRemoved = new HashSet <YDoc>(); SubdocsLoaded = new HashSet <YDoc>(); }
public void TestRestoreDeletedItem1() { var doc = new YDoc(new YDocOptions { Gc = false }); doc.GetArray("array").Insert(0, new[] { "item1", "item2" }); var snap = doc.CreateSnapshot(); doc.GetArray("array").Delete(0); var docRestored = snap.RestoreDocument(doc); CollectionAssert.AreEqual(new[] { "item1", "item2" }, (ICollection)docRestored.GetArray("array").ToArray()); CollectionAssert.AreEqual(new[] { "item2" }, (ICollection)doc.GetArray("array").ToArray()); }
public void TestBasicRestoreSnapshot() { var doc = new YDoc(new YDocOptions { Gc = false }); doc.GetArray("array").Insert(0, new[] { "hello" }); var snap = doc.CreateSnapshot(); doc.GetArray("array").Insert(1, new[] { "world" }); var docRestored = snap.RestoreDocument(doc); CollectionAssert.AreEqual(new[] { "hello" }, (ICollection)docRestored.GetArray("array").ToArray()); CollectionAssert.AreEqual(new[] { "hello", "world" }, (ICollection)doc.GetArray("array").ToArray()); }
public void TestRestoreLeftItem() { var doc = new YDoc(new YDocOptions { Gc = false }); doc.GetArray("array").Insert(0, new[] { "item1" }); doc.GetMap("map").Set("test", 1); doc.GetArray("array").Insert(0, new[] { "item0" }); var snap = doc.CreateSnapshot(); doc.GetArray("array").Delete(1); var docRestored = snap.RestoreDocument(doc); CollectionAssert.AreEqual(new[] { "item0", "item1" }, (ICollection)docRestored.GetArray("array").ToArray()); CollectionAssert.AreEqual(new[] { "item0" }, (ICollection)doc.GetArray("array").ToArray()); }
public void TestDoubleUndo() { var doc = new YDoc(); var text = doc.GetText(); text.Insert(0, "1221"); var undoManager = new UndoManager(text); text.Insert(2, "3"); text.Insert(3, "3"); undoManager.Undo(); undoManager.Undo(); text.Insert(2, "3"); Assert.AreEqual("12321", text.ToString()); }
public void TestEmptyRestoreSnapshot() { var doc = new YDoc(new YDocOptions { Gc = false }); var snap = doc.CreateSnapshot(); snap.StateVector[9999] = 0; doc.GetArray().Insert(0, new[] { "world" }); var docRestored = snap.RestoreDocument(doc); Assert.AreEqual(0, docRestored.GetArray().ToArray().Count); CollectionAssert.AreEqual(new[] { "world" }, (ICollection)doc.GetArray().ToArray()); // Now this snapshot reflects the latest state. It should still work. var snap2 = doc.CreateSnapshot(); var docRestored2 = snap2.RestoreDocument(doc); CollectionAssert.AreEqual(new[] { "world" }, (ICollection)docRestored2.GetArray().ToArray()); }
public void TestIteratingArrayContainingTypes() { var y = new YDoc(); var arr = y.GetArray("arr"); const int numItems = 10; for (int i = 0; i < numItems; i++) { var map = new YMap(); map.Set("value", i); arr.Add(new[] { map }); } int cnt = 0; foreach (var item in arr) { Assert.AreEqual(cnt++, (item as YMap).Get("value")); } y.Destroy(); }
public static int ReadSyncMessage(Stream reader, Stream writer, YDoc doc, object transactionOrigin) { var messageType = (int)reader.ReadVarUint(); switch (messageType) { case MessageYjsSyncStep1: ReadSyncStep1(reader, writer, doc); break; case MessageYjsSyncStep2: ReadSyncStep2(reader, doc, transactionOrigin); break; case MessageYjsUpdate: ReadUpdate(reader, doc, transactionOrigin); break; default: throw new Exception($"Unknown message type: {messageType}"); } return(messageType); }
public void TestSubdoc() { var doc = new YDoc(); doc.Load(); { List <List <string> > events = null; doc.SubdocsChanged += (s, e) => { events = new List <List <string> >(); events.Add(new List <string>(e.Added.Select(d => d.Guid))); events.Add(new List <string>(e.Removed.Select(d => d.Guid))); events.Add(new List <string>(e.Loaded.Select(d => d.Guid))); }; var subdocs = doc.GetMap("mysubdocs"); var docA = new YDoc(new YDocOptions { Guid = "a" }); docA.Load(); subdocs.Set("a", docA); CollectionAssert.AreEqual(new[] { "a" }, events[0]); CollectionAssert.AreEqual(new object[] { }, events[1]); CollectionAssert.AreEqual(new[] { "a" }, events[2]); events = null; (subdocs.Get("a") as YDoc).Load(); Assert.IsNull(events); events = null; (subdocs.Get("a") as YDoc).Destroy(); CollectionAssert.AreEqual(new[] { "a" }, events[0]); CollectionAssert.AreEqual(new[] { "a" }, events[1]); CollectionAssert.AreEqual(new object[] { }, events[2]); events = null; (subdocs.Get("a") as YDoc).Load(); CollectionAssert.AreEqual(new object[] { }, events[0]); CollectionAssert.AreEqual(new object[] { }, events[1]); CollectionAssert.AreEqual(new[] { "a" }, events[2]); events = null; subdocs.Set("b", new YDoc(new YDocOptions { Guid = "a" })); CollectionAssert.AreEqual(new[] { "a" }, events[0]); CollectionAssert.AreEqual(new object[] { }, events[1]); CollectionAssert.AreEqual(new object[] { }, events[2]); events = null; (subdocs.Get("b") as YDoc).Load(); CollectionAssert.AreEqual(new object[] { }, events[0]); CollectionAssert.AreEqual(new object[] { }, events[1]); CollectionAssert.AreEqual(new[] { "a" }, events[2]); events = null; var docC = new YDoc(new YDocOptions { Guid = "c" }); docC.Load(); subdocs.Set("c", docC); CollectionAssert.AreEqual(new[] { "c" }, events[0]); CollectionAssert.AreEqual(new object[] { }, events[1]); CollectionAssert.AreEqual(new[] { "c" }, events[2]); events = null; var guids = doc.GetSubdocGuids().ToList(); guids.Sort(); CollectionAssert.AreEqual(new[] { "a", "c" }, guids); } var doc2 = new YDoc(); { Assert.AreEqual(0, doc2.GetSubdocGuids().Count()); List <List <string> > events = null; doc2.SubdocsChanged += (s, e) => { events = new List <List <string> >(); events.Add(new List <string>(e.Added.Select(d => d.Guid))); events.Add(new List <string>(e.Removed.Select(d => d.Guid))); events.Add(new List <string>(e.Loaded.Select(d => d.Guid))); }; doc2.ApplyUpdateV2(doc.EncodeStateAsUpdateV2()); CollectionAssert.AreEqual(new[] { "a", "a", "c" }, events[0]); CollectionAssert.AreEqual(new object[] { }, events[1]); CollectionAssert.AreEqual(new object[] { }, events[2]); events = null; (doc2.GetMap("mysubdocs").Get("a") as YDoc).Load(); CollectionAssert.AreEqual(new object[] { }, events[0]); CollectionAssert.AreEqual(new object[] { }, events[1]); CollectionAssert.AreEqual(new[] { "a" }, events[2]); events = null; var guids = doc2.GetSubdocGuids().ToList(); guids.Sort(); CollectionAssert.AreEqual(new[] { "a", "c" }, guids); doc2.GetMap("mysubdocs").Delete("a"); CollectionAssert.AreEqual(new object[] { }, events[0]); CollectionAssert.AreEqual(new[] { "a" }, events[1]); CollectionAssert.AreEqual(new object[] { }, events[2]); events = null; guids = doc2.GetSubdocGuids().ToList(); guids.Sort(); CollectionAssert.AreEqual(new[] { "a", "c" }, guids); } }
internal virtual void Integrate(YDoc doc, Item item) { Doc = doc; _item = item; }
public static AbsolutePosition TryCreateFromAbsolutePosition(RelativePosition rpos, YDoc doc) { var store = doc.Store; var rightId = rpos.Item; var typeId = rpos.TypeId; var tName = rpos.TName; int index = 0; AbstractType type; if (rightId != null) { if (store.GetState(rightId.Value.Client) <= rightId.Value.Clock) { return(null); } var res = store.FollowRedone(rightId.Value); var right = res.item as Item; if (right == null) { return(null); } type = right.Parent as AbstractType; Debug.Assert(type != null); if (type._item == null || !type._item.Deleted) { index = right.Deleted || !right.Countable ? 0 : res.diff; var n = right.Left as Item; while (n != null) { if (!n.Deleted && n.Countable) { index += n.Length; } n = n.Left as Item; } } } else { if (tName != null) { type = doc.Get <AbstractType>(tName); } else if (typeId != null) { if (store.GetState(typeId.Value.Client) <= typeId.Value.Clock) { // Type does not exist yet. return(null); } var item = store.FollowRedone(typeId.Value).item as Item; if (item != null && item.Content is ContentType) { type = (item.Content as ContentType).Type; } else { // Struct is garbage collected. return(null); } } else { throw new Exception(); } index = type.Length; } return(new AbsolutePosition(type, index)); }
internal override void Integrate(YDoc doc, Item item) { base.Integrate(doc, item); Insert(0, _prelimContent); _prelimContent = null; }
internal static void CleanupTransactions(IList <Transaction> transactionCleanups, int i) { if (i < transactionCleanups.Count) { var transaction = transactionCleanups[i]; var doc = transaction.Doc; var store = doc.Store; var ds = transaction.DeleteSet; var mergeStructs = transaction._mergeStructs; var actions = new List <Action>(); try { ds.SortAndMergeDeleteSet(); transaction.AfterState = store.GetStateVector(); doc._transaction = null; actions.Add(() => { doc.InvokeOnBeforeObserverCalls(transaction); }); actions.Add(() => { foreach (var kvp in transaction.Changed) { var itemType = kvp.Key; var subs = kvp.Value; if (itemType._item == null || !itemType._item.Deleted) { itemType.CallObserver(transaction, subs); } } }); actions.Add(() => { // Deep observe events. foreach (var kvp in transaction.ChangedParentTypes) { var type = kvp.Key; var events = kvp.Value; // We need to think about the possibility that the user transforms the YDoc in the event. if (type._item == null || !type._item.Deleted) { foreach (var evt in events) { if (evt.Target._item == null || !evt.Target._item.Deleted) { evt.CurrentTarget = type; } } // Sort events by path length so that top-level events are fired first. var sortedEvents = events.ToList(); sortedEvents.Sort((a, b) => a.Path.Count - b.Path.Count); Debug.Assert(sortedEvents.Count > 0); actions.Add(() => { type.CallDeepEventHandlerListeners(sortedEvents, transaction); }); } } }); actions.Add(() => { doc.InvokeOnAfterTransaction(transaction); }); CallAll(actions); } finally { // Replace deleted items with ItemDeleted / GC. // This is where content is actually removed from the Yjs Doc. if (doc.Gc) { ds.TryGcDeleteSet(store, doc.GcFilter); } ds.TryMergeDeleteSet(store); // On all affected store.clients props, try to merge. foreach (var kvp in transaction.AfterState) { var client = kvp.Key; var clock = kvp.Value; if (!transaction.BeforeState.TryGetValue(client, out int beforeClock)) { beforeClock = 0; } if (beforeClock != clock) { var structs = store.Clients[client]; var firstChangePos = Math.Max(StructStore.FindIndexSS(structs, beforeClock), 1); for (int j = structs.Count - 1; j >= firstChangePos; j--) { DeleteSet.TryToMergeWithLeft(structs, j); } } } // Try to merge mergeStructs. // TODO: It makes more sense to transform mergeStructs to a DS, sort it, and merge from right to left // but at the moment DS does not handle duplicates. for (int j = 0; j < mergeStructs.Count; j++) { var client = mergeStructs[j].Id.Client; var clock = mergeStructs[j].Id.Clock; var structs = store.Clients[client]; var replacedStructPos = StructStore.FindIndexSS(structs, clock); if (replacedStructPos + 1 < structs.Count) { DeleteSet.TryToMergeWithLeft(structs, replacedStructPos + 1); } if (replacedStructPos > 0) { DeleteSet.TryToMergeWithLeft(structs, replacedStructPos); } } if (!transaction.Local) { if (!transaction.AfterState.TryGetValue(doc.ClientId, out int afterClock)) { afterClock = -1; } if (!transaction.BeforeState.TryGetValue(doc.ClientId, out int beforeClock)) { beforeClock = -1; } if (afterClock != beforeClock) { doc.ClientId = YDoc.GenerateNewClientId(); // Debug.WriteLine($"{nameof(Transaction)}: Changed the client-id because another client seems to be using it."); } } // @todo: Merge all the transactions into one and provide send the data as a single update message. doc.InvokeOnAfterTransactionCleanup(transaction); doc.InvokeUpdateV2(transaction); foreach (var subDoc in transaction.SubdocsAdded) { doc.Subdocs.Add(subDoc); } foreach (var subDoc in transaction.SubdocsRemoved) { doc.Subdocs.Remove(subDoc); } doc.InvokeSubdocsChanged(transaction.SubdocsLoaded, transaction.SubdocsAdded, transaction.SubdocsRemoved); foreach (var subDoc in transaction.SubdocsRemoved) { subDoc.Destroy(); } if (transactionCleanups.Count <= i + 1) { doc._transactionCleanups.Clear(); doc.InvokeAfterAllTransactions(transactionCleanups); } else { CleanupTransactions(transactionCleanups, i + 1); } } } }
public static void ReadUpdate(Stream stream, YDoc doc, object transactionOrigin) { ReadSyncStep2(stream, doc, transactionOrigin); }
public static void ReadSyncStep2(Stream stream, YDoc doc, object transactionOrigin) { var update = stream.ReadVarUint8Array(); doc.ApplyUpdateV2(update, transactionOrigin); }
public static IDictionary <int, List <AbstractStruct> > ReadClientStructRefs(IUpdateDecoder decoder, YDoc doc) { var clientRefs = new Dictionary <int, List <AbstractStruct> >(); var numOfStateUpdates = decoder.Reader.ReadVarUint(); for (int i = 0; i < numOfStateUpdates; i++) { var numberOfStructs = (int)decoder.Reader.ReadVarUint(); Debug.Assert(numberOfStructs >= 0); var refs = new List <AbstractStruct>(numberOfStructs); var client = decoder.ReadClient(); var clock = (int)decoder.Reader.ReadVarUint(); clientRefs[client] = refs; for (int j = 0; j < numberOfStructs; j++) { var info = decoder.ReadInfo(); if ((Bits.Bits5 & info) != 0) { // The item that was originally to the left of this item. var leftOrigin = (info & Bit.Bit8) == Bit.Bit8 ? (ID?)decoder.ReadLeftId() : null; // The item that was originally to the right of this item. var rightOrigin = (info & Bit.Bit7) == Bit.Bit7 ? (ID?)decoder.ReadRightId() : null; var cantCopyParentInfo = (info & (Bit.Bit7 | Bit.Bit8)) == 0; var hasParentYKey = cantCopyParentInfo ? decoder.ReadParentInfo() : false; // If parent == null and neither left nor right are defined, then we know that 'parent' is child of 'y' // and we read the next string as parentYKey. // It indicates how we store/retrieve parent from 'y.share'. var parentYKey = cantCopyParentInfo && hasParentYKey?decoder.ReadString() : null; var str = new Item( new ID(client, clock), null, // left leftOrigin, null, // right rightOrigin, // rightOrigin cantCopyParentInfo && !hasParentYKey ? decoder.ReadLeftId() : (parentYKey != null ? (object)doc.Get <AbstractType>(parentYKey) : null), // parent cantCopyParentInfo && (info & Bit.Bit6) == Bit.Bit6 ? decoder.ReadString() : null, // parentSub ReadItemContent(decoder, info) // content ); refs.Add(str); clock += str.Length; } else { var length = decoder.ReadLength(); refs.Add(new GC(new ID(client, clock), length)); clock += length; } } } return(clientRefs); }
public static void ReadSyncStep1(Stream reader, Stream writer, YDoc doc) { var encodedStateVector = reader.ReadVarUint8Array(); WriteSyncStep2(writer, doc, encodedStateVector); }