private void WriteEntryToNewPage(
            AtomEntry entry,
            AppendContext context)
        {
            var newAddress = this.CreateNewFeedAddress();
            var newPage    = this.ReadPage(newAddress);

            newPage = AddEntryTo(newPage, entry, context.Now);

            var nextLink = AtomLink.CreateNextLink(newAddress);

            var previousPage = context.LastPage
                               .WithLinks(context.LastPage.Links.Concat(new[] { nextLink }));

            var previousLink = previousPage.Links
                               .Single(l => l.IsSelfLink)
                               .ToPreviousLink();

            newPage = newPage.WithLinks(
                newPage.Links.Concat(new[] { previousLink }));
            var index = context.Index.WithLinks(context.Index.Links
                                                .Where(l => !l.IsLastLink)
                                                .Concat(new[] { nextLink.ToLastLink() }));

            this.Write(newPage);
            this.Write(previousPage);
            try { this.Write(index); } catch { }
        }
Esempio n. 2
0
        /// <summary>
        /// Appends an event to the event stream.
        /// </summary>
        /// <param name="event">
        /// The event to append to the event stream.
        /// </param>
        /// <returns>
        /// A <see cref="Task" /> representing the asynchronous operation of
        /// appending the event to the event stream.
        /// </returns>
        /// <remarks>
        /// <para>
        /// This method appends <paramref name="event" /> to the current event
        /// stream. Appending an event indicates that it happened
        /// <em>after</em> all previous events.
        /// </para>
        /// <para>
        /// Since this method conceptually involves writing the event to the
        /// underlying <see cref="Storage" />, it may take significant time to
        /// complete; for that reason, it's an asynchronous method, returning a
        /// <see cref="Task" />. The operation is not guaranteed to be complete
        /// before the Task completes successfully.
        /// </para>
        /// <para>
        /// When updating the underlying Storage, the method typically only
        /// updates the index feed, using
        /// <see cref="IAtomEventStorage.CreateFeedWriterFor(AtomFeed)" />.
        /// However, when the number of entries in the index surpasses
        /// <see cref="PageSize" />, a new feed page is created for the new
        /// entry. This page is also written using the CreateFeedWriterFor
        /// method, and only after this succeeds is the old feed page updated
        /// with a link to the new page. Since these two operations are not
        /// guaranteed to happen within an ACID transaction, it's possible that
        /// the new page is saved, but that the update of the old page fails.
        /// If the underlying storage throws an exception at that point, that
        /// exception will bubble up to the caller of the AppendAsync method.
        /// It's up to the caller to retry the operation.
        /// </para>
        /// <para>
        /// However, in that situation, an orphaned Atom feed page is likely to
        /// have been left in the underlying storage. This doesn't affect
        /// consistency of the system, but may take up unnecessary disk space.
        /// If this is the case, a separate clean-up task should find and
        /// delete orphaned pages.
        /// </para>
        /// </remarks>
        /// <example>
        /// This example shows how to create a UserCreated event and write it
        /// using the AppendAsync method. Notice that since
        /// AtomEventObserver&lt;T&gt; uses the standard Task Parallel Library
        /// (TPL) model, you can use it with 'async' and 'await'.
        /// <code>
        /// var obs = new AtomEventObserver&lt;IUserEvent&gt;(
        ///     eventStreamId, // a Guid
        ///     pageSize,      // an Int32
        ///     storage,       // an IAtomEventStorage object
        ///     serializer);   // an IContentSerializer object
        ///
        /// var userCreated = new UserCreated
        /// {
        ///     UserId = eventStreamId,
        ///     UserName = "******",
        ///     Password = "******",
        ///     Email = "*****@*****.**"
        /// };
        /// await obs.AppendAsync(userCreated);
        /// </code>
        /// </example>
        /// <seealso cref="OnNext" />
        public Task AppendAsync(T @event)
        {
            return(Task.Factory.StartNew(() =>
            {
                var now = DateTimeOffset.Now;

                var index = this.ReadIndex();
                var firstLink = index.Links
                                .Where(l => l.IsFirstLink)
                                .DefaultIfEmpty(AtomLink.CreateFirstLink(this.CreateNewFeedAddress()))
                                .Single();
                var lastLink = index.Links.SingleOrDefault(l => l.IsLastLink);
                var lastLinkChanged = false;
                if (lastLink == null)
                {
                    lastLink = firstLink.ToLastLink();
                    lastLinkChanged = true;
                }
                var lastPage = this.ReadLastPage(lastLink.Href);
                if (lastPage.Links.Single(l => l.IsSelfLink).Href != lastLink.Href)
                {
                    lastLink = lastPage.Links.Single(l => l.IsSelfLink).ToLastLink();
                    lastLinkChanged = true;
                }
                index = index.WithLinks(index.Links.Union(new[] { firstLink }));
                index = index.WithLinks(index.Links
                                        .Where(l => !l.IsLastLink)
                                        .Concat(new[] { lastLink }));

                var entry = CreateEntry(@event, now);

                if (lastPage.Entries.Count() >= this.pageSize)
                {
                    var nextAddress = this.CreateNewFeedAddress();
                    var nextPage = this.ReadPage(nextAddress);
                    nextPage = AddEntryTo(nextPage, entry, now);

                    var nextLink = AtomLink.CreateNextLink(nextAddress);

                    var previousPage = lastPage
                                       .WithLinks(lastPage.Links.Concat(new[] { nextLink }));

                    var previousLink = previousPage.Links
                                       .Single(l => l.IsSelfLink)
                                       .ToPreviousLink();

                    nextPage = nextPage.WithLinks(
                        nextPage.Links.Concat(new[] { previousLink }));
                    index = index.WithLinks(index.Links
                                            .Where(l => !l.IsLastLink)
                                            .Concat(new[] { nextLink.ToLastLink() }));

                    this.Write(nextPage);
                    this.Write(previousPage);
                    this.Write(index);
                }
                else
                {
                    lastPage = AddEntryTo(lastPage, entry, now);

                    this.Write(lastPage);
                    if (lastLinkChanged)
                    {
                        this.Write(index);
                    }
                }
            }));
        }