internal static DocumentSnapshot ForMissingDocument(FirestoreDb db, string name, Timestamp readTime) =>
 new DocumentSnapshot(db, name, null, readTime);
 private DocumentSnapshot(FirestoreDb db, string resourceName, Document document, Timestamp readTime)
 {
     GaxPreconditions.CheckNotNull(db, nameof(db));
     GaxPreconditions.CheckNotNull(resourceName, nameof(resourceName));
     Reference  = db.GetDocumentReferenceFromResourceName(resourceName);
     Document   = document;
     CreateTime = Timestamp.FromProtoOrNull(document?.CreateTime);
     UpdateTime = Timestamp.FromProtoOrNull(document?.UpdateTime);
     ReadTime   = readTime;
 }
 internal static DocumentSnapshot ForDocument(FirestoreDb db, Document document, Timestamp readTime) =>
 new DocumentSnapshot(db, document.Name, document, readTime);
        //
        // CreateDocumentSnapshot(DocumentChange documentChange)
        //
        // Generate a DocumentSnapshot from a Document
        private DocumentSnapshot CreateDocumentSnapshot(Document document)
        {
            //Creation of DocumentSnapshots is internal so we are very much cheating and using reflection to access them
            var snapshotType = typeof(DocumentSnapshot);
            var forDocument  = snapshotType.GetMethod("ForDocument", BindingFlags.Static | BindingFlags.NonPublic);
            //It's probably bad to use the current timestamp, but we don't seem to have access to the readTime in ListenResponse
            DocumentSnapshot snapshot = null;

            try
            {
                snapshot = forDocument.Invoke(null, new object[] { FirestoreDb, document, Timestamp.GetCurrentTimestamp() }) as DocumentSnapshot;
            }
            catch (Exception ex)
            {
                OnError(ex.ToString());
            }
            return(snapshot);
        }
Exemple #5
0
        public async Task <WatchResponseResult> HandleResponseAsync(ListenResponse response, CancellationToken cancellationToken)
        {
            switch (response.ResponseTypeCase)
            {
            case ListenResponse.ResponseTypeOneofCase.TargetChange:
                TargetChange change      = response.TargetChange;
                bool         noTargetIds = change.TargetIds.Count == 0;

                switch (change.TargetChangeType)
                {
                case NoChange:
                    if (noTargetIds && change.ReadTime != null && _current)
                    {
                        // This means everything is up-to-date, so emit the current set of docs as a snapshot,
                        // if there were changes.
                        await PushSnapshotAsync(Timestamp.FromProto(change.ReadTime), change.ResumeToken, cancellationToken).ConfigureAwait(false);
                    }
                    break;

                case Add:
                    GaxPreconditions.CheckState(!noTargetIds && WatchStream.WatchTargetId == change.TargetIds[0], "Target ID must be 0x{0:x}", WatchStream.WatchTargetId);
                    break;

                case Remove:
                    GaxPreconditions.CheckState(!noTargetIds && WatchStream.WatchTargetId == change.TargetIds[0], "Target ID must be 0x{0:x}", WatchStream.WatchTargetId);
                    // This may not be the right kind of exception to throw, but whatever we throw should be a permanent error. This is a reasonable starting point.
                    throw new InvalidOperationException("Server removed watch target");

                case Current:
                    _current = true;
                    break;

                case Reset:
                    ResetDocs();
                    return(WatchResponseResult.Continue);

                default:
                    throw new InvalidOperationException($"Encountered invalid target change type: {change.Cause.Message}");
                }

                bool healthy = change.ResumeToken != null && (change.TargetIds.Count == 0 || change.TargetIds.Contains(WatchStream.WatchTargetId));
                // Possibly tell the watch stream that it's now healthy (so reset backoff), or just continue.
                return(healthy ? WatchResponseResult.StreamHealthy : WatchResponseResult.Continue);

            case ListenResponse.ResponseTypeOneofCase.DocumentChange:
                // No other targetIds can show up here, but we still need to see if the targetId was in the
                // added list or removed list.
                var               changed  = response.DocumentChange.TargetIds.Contains(WatchStream.WatchTargetId);
                var               removed  = response.DocumentChange.RemovedTargetIds.Contains(WatchStream.WatchTargetId);
                Document          document = response.DocumentChange.Document;
                DocumentReference docRef   = CreateDocumentReference(document.Name);
                if (changed && removed)
                {
                    throw new InvalidOperationException("Server error: document was both changed and removed");
                }
                if (!changed && !removed)
                {
                    // This is probably an error in the server, but we can follow protocol by just ignoring this response.
                    return(WatchResponseResult.Continue);
                }
                _changeMap[docRef] = changed ? document : null;
                return(WatchResponseResult.Continue);

            case ListenResponse.ResponseTypeOneofCase.DocumentDelete:
                _changeMap[CreateDocumentReference(response.DocumentDelete.Document)] = null;
                return(WatchResponseResult.Continue);

            case ListenResponse.ResponseTypeOneofCase.DocumentRemove:
                _changeMap[CreateDocumentReference(response.DocumentRemove.Document)] = null;
                return(WatchResponseResult.Continue);

            case ListenResponse.ResponseTypeOneofCase.Filter:
                // TODO: Do we really want to create the change set itself, rather than just count? It seems a bit wasteful.
                ChangeSet changeSet   = ExtractChanges(default);
                int       currentSize = _documentSet.Count + changeSet.Adds.Count - changeSet.Deletes.Count;
                // Reset the stream if we don't have the right number of documents.
                if (response.Filter.Count != currentSize)
                {
                    ResetDocs();
                    return(WatchResponseResult.ResetStream);
                }
                return(WatchResponseResult.Continue);

            default:
                throw new InvalidOperationException($"Encountered invalid listen response type: {response.ResponseTypeCase}");
            }
        }
 /// <summary>
 /// Creates a precondition that the document has the specified last update time.
 /// </summary>
 public static Precondition LastUpdated(Timestamp timestamp) =>
 new Precondition(new V1Beta1.Precondition {
     UpdateTime = timestamp.ToProto()
 });
Exemple #7
0
 internal static WriteResult FromProto(V1Beta1.WriteResult result, wkt::Timestamp commitTime)
 {
     GaxPreconditions.CheckNotNull(result, nameof(result));
     GaxPreconditions.CheckNotNull(commitTime, nameof(commitTime));
     return(new WriteResult(Timestamp.FromProto(result.UpdateTime ?? commitTime)));
 }
Exemple #8
0
 private WriteResult(Timestamp updateTime)
 {
     UpdateTime = updateTime;
 }