Example #1
0
 private void CheckEditOptions(EntityEditOptions options)
 {
     if ((options & (EntityEditOptions.Bot | EntityEditOptions.Progressive
                     | EntityEditOptions.Bulk | EntityEditOptions.ClearData
                     | EntityEditOptions.StrictEditConflictDetection)) != options)
     {
         throw new ArgumentOutOfRangeException(nameof(options));
     }
     if ((options & EntityEditOptions.Progressive) == EntityEditOptions.Progressive &&
         (options & EntityEditOptions.Bulk) == EntityEditOptions.Bulk)
     {
         throw new ArgumentException("EntityEditOptions.Progressive and EntityEditOptions.Bulk cannot be specified at the same time.");
     }
 }
Example #2
0
        public async Task EditEntityTest1(EntityEditOptions options)
        {
            AssertModify();

            const string ArbitaryItemEntityId = "Q487"; // An item ID that exists on test wiki site.

            options |= EntityEditOptions.Bot;
            var site = await WikidataTestSiteAsync;
            var rand = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
            // Create
            var entity     = new Entity(site, EntityType.Item);
            var changelist = new[]
            {
                new EntityEditEntry(nameof(Entity.Labels), new WbMonolingualText("en", "test entity " + rand)),
                new EntityEditEntry(nameof(Entity.Aliases), new WbMonolingualText("en", "entity for test")),
                new EntityEditEntry(nameof(Entity.Aliases), new WbMonolingualText("en", "test")), new EntityEditEntry(nameof(Entity.Descriptions),
                                                                                                                      new WbMonolingualText("en",
                                                                                                                                            "This is a test entity for unit test. If you see this entity outside the test site, please check the revision history and notify the editor.")),
                new EntityEditEntry(nameof(Entity.Descriptions), new WbMonolingualText("zh", "此实体仅用于测试之用。如果你在非测试维基见到此实体,请检查修订历史并告知编辑者。")),
                new EntityEditEntry(nameof(entity.SiteLinks), new EntitySiteLink("testwiki", "Foo")),
            };
            await entity.EditAsync(changelist, "Create test entity.", options);

            if ((options & EntityEditOptions.Bulk) != EntityEditOptions.Bulk)
            {
                await entity.RefreshAsync(EntityQueryOptions.FetchLabels
                                          | EntityQueryOptions.FetchDescriptions
                                          | EntityQueryOptions.FetchAliases
                                          | EntityQueryOptions.FetchSiteLinks);
            }
            ShallowTrace(entity);
            Assert.Equal("test entity " + rand, entity.Labels["en"]);
            Assert.Contains("test", entity.Aliases["en"]);
            Assert.Contains("This is a test entity", entity.Descriptions["en"]);
            Assert.Contains("此实体仅用于测试之用。", entity.Descriptions["zh"]);
            Assert.Equal("Foo", entity.SiteLinks["testwiki"].Title);

            // General edit
            changelist = new[]
            {
                new EntityEditEntry(nameof(Entity.Labels), new WbMonolingualText("zh-hans", "测试实体" + rand)),
                new EntityEditEntry(nameof(Entity.Labels), new WbMonolingualText("zh-hant", "測試實體" + rand)),
                // One language can have multiple aliases, so we need to specify which alias to remove, instead of using "dummy" here.
                new EntityEditEntry(nameof(Entity.Aliases), new WbMonolingualText("en", "Test"), EntityEditEntryState.Removed),
                new EntityEditEntry(nameof(Entity.Descriptions), new WbMonolingualText("zh", "dummy"), EntityEditEntryState.Removed),
                new EntityEditEntry(nameof(Entity.SiteLinks), new EntitySiteLink("testwiki", "dummy"), EntityEditEntryState.Removed),
            };
            await entity.EditAsync(changelist, "Edit test entity.", options);

            if ((options & EntityEditOptions.Bulk) != EntityEditOptions.Bulk)
            {
                await entity.RefreshAsync(EntityQueryOptions.FetchLabels
                                          | EntityQueryOptions.FetchDescriptions
                                          | EntityQueryOptions.FetchAliases
                                          | EntityQueryOptions.FetchSiteLinks);
            }
            ShallowTrace(entity);
            Assert.Null(entity.Descriptions["zh"]);
            Assert.Equal("测试实体" + rand, entity.Labels["zh-hans"]);
            Assert.Equal("測試實體" + rand, entity.Labels["zh-hant"]);
            Assert.DoesNotContain("Test", entity.Aliases["en"]);
            Assert.False(entity.SiteLinks.ContainsKey("testwiki"));

            // Add claim
            //  Create a property first.
            var prop = new Entity(site, EntityType.Property);

            changelist = new[]
            {
                new EntityEditEntry(nameof(Entity.Labels), new WbMonolingualText("en", "test property " + rand)),
                new EntityEditEntry(nameof(Entity.DataType), BuiltInDataTypes.WikibaseItem),
            };
            await prop.EditAsync(changelist, "Create a property for test.");

            // Refill basic information, esp. WbEntity.DataType
            await prop.RefreshAsync(EntityQueryOptions.FetchInfo);

            //  Add the claims.
            changelist = new[]
            {
                new EntityEditEntry(nameof(Entity.Claims), new Claim(new Snak(prop, entity.Id))), new EntityEditEntry(nameof(Entity.Claims),
                                                                                                                      new Claim(new Snak(prop, entity.Id))
                {
                    Qualifiers = { new Snak(prop, ArbitaryItemEntityId) }
                }),
                new EntityEditEntry(nameof(Entity.Claims),
                                    new Claim(new Snak(prop, ArbitaryItemEntityId))
                {
                    References = { new ClaimReference(new Snak(prop, entity.Id)) }
                }),
            };
            await entity.EditAsync(changelist, "Edit test entity. Add claims.", options);

            if ((options & EntityEditOptions.Bulk) != EntityEditOptions.Bulk)
            {
                await entity.RefreshAsync(EntityQueryOptions.FetchClaims);
            }
            ShallowTrace(entity);

            Assert.Equal(3, entity.Claims.Count);
            Assert.Equal(3, entity.Claims[prop.Id].Count);
            Assert.Contains(entity.Claims[prop.Id], c => entity.Id.Equals(c.MainSnak.DataValue));
            var claim2 = entity.Claims[prop.Id].FirstOrDefault(c => ArbitaryItemEntityId.Equals(c.MainSnak.DataValue));

            Assert.NotNull(claim2);
            Assert.Equal(entity.Id, claim2.References[0].Snaks[0].DataValue);

            //  Check consistency on the claim with qualifier
            var qualifiedClaim = entity.Claims[prop.Id].Single(c => c.Qualifiers.Count > 0);

            Assert.Equal(entity.Id, qualifiedClaim.MainSnak.DataValue);
            Assert.Single(qualifiedClaim.Qualifiers);
            Assert.Equal(prop.Id, qualifiedClaim.Qualifiers[0].PropertyId);
            Assert.Equal(ArbitaryItemEntityId, qualifiedClaim.Qualifiers[0].DataValue);

            //  Update claim qualifier
            qualifiedClaim.Qualifiers[0].DataValue = entity.Id;
            changelist = new[] { new EntityEditEntry(nameof(Entity.Claims), qualifiedClaim) };
            await entity.EditAsync(changelist, "Edit test entity. Update qualifier.", options);

            ShallowTrace(entity);

            // Remove a claim (claim2 contains the claim id now).
            changelist = new[] { new EntityEditEntry(nameof(Entity.Claims), claim2, EntityEditEntryState.Removed), };
            await entity.EditAsync(changelist, "Edit test entity. Remove a claim.", options);

            if ((options & EntityEditOptions.Bulk) != EntityEditOptions.Bulk)
            {
                await entity.RefreshAsync(EntityQueryOptions.FetchClaims);
            }
            Assert.Equal(2, entity.Claims.Count);
            Assert.All(entity.Claims[prop.Id], c => Assert.Equal(entity.Id, c.MainSnak.DataValue));
            Assert.Contains(entity.Claims[prop.Id], c => c.Qualifiers.Count == 0);
            Assert.Contains(entity.Claims[prop.Id], c => c.Qualifiers.Count == 1 && entity.Id.Equals(c.Qualifiers[0].DataValue));
        }
 /// <inheritdoc cref="EditAsync(IEnumerable{EntityEditEntry},string,EntityEditOptions,CancellationToken)"/>
 public Task EditAsync(IEnumerable <EntityEditEntry> edits, string summary, EntityEditOptions options)
 {
     return(EditAsync(edits, summary, options, CancellationToken.None));
 }
Example #4
0
        /// <summary>
        /// Makes the specified changes to the current entity on the Wikibase site.
        /// </summary>
        /// <param name="edits">The changes to be made.</param>
        /// <param name="summary">The edit summary.</param>
        /// <param name="options">Edit options.</param>
        /// <param name="cancellationToken">A token used to cancel the operation.</param>
        /// <exception cref="ArgumentNullException">Either <paramref name="edits"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentException"><paramref name="options"/> is invalid.</exception>
        /// <exception cref="NotSupportedException">Attempt to set <see cref="DataType"/> when editing an existing property entity, or force progressive edits when creating a property entity.</exception>
        /// <exception cref="OperationConflictException">Edit conflict detected.</exception>
        /// <exception cref="UnauthorizedOperationException">You have no rights to edit the page.</exception>
        /// <remarks><para>After the operation, the entity may be automatically refreshed,
        /// which means all the <see cref="Claim"/> instances that used to belong to this claim will be detached,
        /// and perhaps replicates will take the place.
        /// If the edit operation is bulk edit, this is effectively a refresh operation with <see cref="EntityQueryOptions.FetchAllProperties"/> flag,
        /// except that some properties in the <see cref="EntityQueryOptions.FetchInfo"/> category are just invalidated
        /// due to insufficient data contained in the MW API. (e.g. <see cref="PageId"/>) As for the properties that are
        /// affected by the edit operation, see the "remarks" section of the properties, respectively.</para>
        /// <para>If the edit operation is progressive edit, only the <see cref="LastRevisionId"/> is valid, after the edit operation.</para>
        /// <para>For more information about bulk edit and progressive edit, see the "remarks" section
        /// of <see cref="EntityEditOptions"/>.</para>
        /// <para>If you need more information about the entity after the edit, consider invoking <see cref="RefreshAsync()"/> again.</para>
        /// </remarks>
        public async Task EditAsync(IEnumerable <EntityEditEntry> edits, string summary, EntityEditOptions options, CancellationToken cancellationToken)
        {
            const int bulkEditThreshold = 5;

            if (edits == null)
            {
                throw new ArgumentNullException(nameof(edits));
            }
            if (Id == null && Type == EntityType.Property && (options & EntityEditOptions.Progressive) == EntityEditOptions.Progressive)
            {
                throw new NotSupportedException("Creating a property is not possible in progressive mode.");
            }
            CheckEditOptions(options);
            cancellationToken.ThrowIfCancellationRequested();
            using (Site.BeginActionScope(this, options))
            {
                var bulk = (options & EntityEditOptions.Bulk) == EntityEditOptions.Bulk;
                if (!bulk && (options & EntityEditOptions.Progressive) != EntityEditOptions.Progressive)
                {
                    // Determine to use bulk/progressive by the item count.
                    if (Id == null)
                    {
                        bulk = true;
                    }
                    else if (edits is IReadOnlyCollection <EntityEditEntry> rc)
                    {
                        bulk = rc.Count >= bulkEditThreshold;
                    }
                    else if (edits is ICollection <EntityEditEntry> c)
                    {
                        bulk = c.Count >= bulkEditThreshold;
                    }
                    else
                    {
                        var items = edits.ToList();
                        bulk = items.Count >= bulkEditThreshold;
                        // Prevent multiple invocation to IEnumerable<T>.GetEnumerator()
                        edits = items;
                    }
                }
                Site.Logger.LogInformation("Editing entity. Bulk={Bulk}.", bulk);
                if (bulk)
                {
                    var contract = SerializeEditEntries(edits);
                    using (await Site.ModificationThrottler.QueueWorkAsync("Edit: " + this, cancellationToken))
                    {
                        var jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new
                        {
                            action = "wbeditentity",
                            token = WikiSiteToken.Edit,
                            id = Id,
                            @new = Id == null ? FormatEntityType(Type) : null,
                            baserevid = LastRevisionId > 0 ? (int?)LastRevisionId : null,
                            bot = (options & EntityEditOptions.Bot) == EntityEditOptions.Bot,
                            summary = summary,
                            clear = (options & EntityEditOptions.ClearData) == EntityEditOptions.ClearData,
                            data = Utility.WikiJsonSerializer.Serialize(contract)
                        }), cancellationToken);

                        var jentity = jresult["entity"];
                        if (jentity == null)
                        {
                            throw new UnexpectedDataException("Missing \"entity\" node in the JSON response.");
                        }
                        LoadFromJson(jentity, EntityQueryOptions.FetchAllProperties, true);
                    }
                }
                else
                {
                    using (await Site.ModificationThrottler.QueueWorkAsync("Progressive edit: " + this, cancellationToken))
                    {
                        await ProgressiveEditAsync(edits, summary,
                                                   (options & EntityEditOptions.Bot) == EntityEditOptions.Bot,
                                                   (options & EntityEditOptions.StrictEditConflictDetection) == EntityEditOptions.StrictEditConflictDetection,
                                                   cancellationToken);
                    }
                }
                Site.Logger.LogInformation("Edited entity. New revid={RevisionId}.", LastRevisionId);
            }
        }