private async Task ProgressiveEditAsync(IEnumerable <EntityEditEntry> edits, string summary, bool isBot, bool strict, CancellationToken cancellationToken) { Debug.Assert(edits != null); var checkbaseRev = true; foreach (var prop in edits.GroupBy(e => e.PropertyName)) { if (prop.Any(p => p.Value == null)) { throw new ArgumentException($"Detected null value in {prop} entries.", nameof(edits)); } switch (prop.Key) { case nameof(DataType): throw new NotSupportedException("Setting data type is not possible in progressive mode."); case nameof(Labels): foreach (var p in prop) { var value = (WbMonolingualText)p.Value; var jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new { action = "wbsetlabel", token = WikiSiteToken.Edit, id = Id, @new = Id == null ? FormatEntityType(Type) : null, baserevid = checkbaseRev && LastRevisionId > 0 ? (int?)LastRevisionId : null, bot = isBot, summary = summary, language = value.Language, value = p.State == EntityEditEntryState.Updated ? value.Text : null, }), cancellationToken); LoadEntityMinimal(jresult["entity"]); if (!strict) { checkbaseRev = false; } } break; case nameof(Descriptions): foreach (var p in prop) { var value = (WbMonolingualText)p.Value; var jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new { action = "wbsetdescription", token = WikiSiteToken.Edit, id = Id, @new = Id == null ? FormatEntityType(Type) : null, baserevid = checkbaseRev && LastRevisionId > 0 ? (int?)LastRevisionId : null, bot = isBot, summary = summary, language = value.Language, value = p.State == EntityEditEntryState.Updated ? value.Text : null, }), cancellationToken); LoadEntityMinimal(jresult["entity"]); if (!strict) { checkbaseRev = false; } } break; case nameof(Aliases): { var entries = prop.GroupBy(t => ((WbMonolingualText)t.Value).Language); foreach (var langGroup in entries) { var addExpr = MediaWikiHelper.JoinValues(langGroup .Where(e => e.State == EntityEditEntryState.Updated) .Select(e => ((WbMonolingualText)e.Value).Text)); var removeExpr = MediaWikiHelper.JoinValues(langGroup .Where(e => e.State == EntityEditEntryState.Removed) .Select(e => ((WbMonolingualText)e.Value).Text)); var jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new { action = "wbsetaliases", token = WikiSiteToken.Edit, id = Id, @new = Id == null ? FormatEntityType(Type) : null, baserevid = checkbaseRev && LastRevisionId > 0 ? (int?)LastRevisionId : null, bot = isBot, summary = summary, language = langGroup.Key, add = addExpr.Length == 0 ? null : addExpr, remove = removeExpr.Length == 0 ? null : removeExpr, }), cancellationToken); LoadEntityMinimal(jresult["entity"]); if (!strict) { checkbaseRev = false; } } break; } case nameof(SiteLinks): { var entries = prop.GroupBy(t => ((EntitySiteLink)t.Value).Site); foreach (var siteGroup in entries) { string link = null, badges = null; try { var item = siteGroup.Single(); if (item.State == EntityEditEntryState.Updated) { var value = (EntitySiteLink)item.Value; link = value.Title; badges = MediaWikiHelper.JoinValues(value.Badges); } } catch (InvalidOperationException) { throw new ArgumentException("One site can own at most one site link.", nameof(edits)); } var jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new { action = "wbsetsitelink", token = WikiSiteToken.Edit, id = Id, @new = Id == null ? FormatEntityType(Type) : null, baserevid = checkbaseRev && LastRevisionId > 0 ? (int?)LastRevisionId : null, bot = isBot, summary = summary, linksite = siteGroup.Key, linktitle = link, badges = badges, }), cancellationToken); LoadEntityMinimal(jresult["entity"]); if (!strict) { checkbaseRev = false; } } break; } case nameof(Claims): foreach (var entry in prop.Where(e => e.State == EntityEditEntryState.Updated)) { var value = (Claim)entry.Value; var claimContract = value.ToContract(false); if (value.Id == null) { // New claim. We need to assign an ID manually. // https://phabricator.wikimedia.org/T182573#3828344 if (Id == null) { // This is a new entity, so we need to create it first. var jresult1 = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new { action = "wbeditentity", token = WikiSiteToken.Edit, @new = FormatEntityType(Type), bot = isBot, summary = (string)null, data = "{}" }), cancellationToken); if (!strict) { checkbaseRev = false; } LoadEntityMinimal(jresult1["entity"]); } claimContract.Id = Utility.NewClaimGuid(Id); } var jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new { action = "wbsetclaim", token = WikiSiteToken.Edit, @new = Id == null ? FormatEntityType(Type) : null, baserevid = checkbaseRev && LastRevisionId > 0 ? (int?)LastRevisionId : null, bot = isBot, summary = summary, claim = Utility.WikiJsonSerializer.Serialize(claimContract), }), cancellationToken); // jresult["claim"] != null LastRevisionId = (int)jresult["pageinfo"]["lastrevid"]; if (!strict) { checkbaseRev = false; } } foreach (var batch in prop.Where(e => e.State == EntityEditEntryState.Removed) .Select(e => ((Claim)e.Value).Id).Partition(50)) { var jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new { action = "wbremoveclaims", token = WikiSiteToken.Edit, id = Id, @new = Id == null ? FormatEntityType(Type) : null, baserevid = checkbaseRev && LastRevisionId > 0 ? (int?)LastRevisionId : null, bot = isBot, summary = summary, claim = MediaWikiHelper.JoinValues(batch), }), cancellationToken); LastRevisionId = (int)jresult["pageinfo"]["lastrevid"]; if (!strict) { checkbaseRev = false; } } break; default: throw new ArgumentException($"Unrecognized {nameof(Entity)} property name: {prop.Key}."); } } void LoadEntityMinimal(JToken jentity) { Debug.Assert(jentity != null); Id = (string)jentity["id"]; Type = SerializableEntity.ParseEntityType((string)jentity["type"]); LastRevisionId = (int)jentity["lastrevid"]; } }
// postEditing: Is the entity param from the response of wbeditentity API call? internal void LoadFromContract(Contracts.Entity entity, EntityQueryOptions options, bool isPostEditing) { var extensionData = entity.ExtensionData ?? emptyExtensionData; var id = entity.Id; Debug.Assert(id != null); if ((options & EntityQueryOptions.SuppressRedirects) != EntityQueryOptions.SuppressRedirects && Id != null && Id != id) { // The page has been overwritten, or deleted. //logger.LogWarning("Detected change of page id for [[{Title}]]: {Id1} -> {Id2}.", Title, Id, id); } var serializable = extensionData.ContainsKey("missing") ? null : SerializableEntity.Load(entity); Id = id; Exists = serializable != null; Type = EntityType.Unknown; PageId = -1; NamespaceId = -1; Title = null; LastModified = DateTime.MinValue; LastRevisionId = 0; Labels = null; Aliases = null; Descriptions = null; SiteLinks = null; QueryOptions = options; if (serializable == null) { return; } serializable = SerializableEntity.Load(entity); Type = serializable.Type; DataType = serializable.DataType; if ((options & EntityQueryOptions.FetchInfo) == EntityQueryOptions.FetchInfo) { if (!isPostEditing) { // wbeditentity response does not have these properties. PageId = (int)extensionData["pageid"]; NamespaceId = (int)extensionData["ns"]; Title = (string)extensionData["title"]; LastModified = (DateTime)extensionData["modified"]; } LastRevisionId = (int)extensionData["lastrevid"]; } if ((options & EntityQueryOptions.FetchLabels) == EntityQueryOptions.FetchLabels) { Labels = serializable.Labels; if (Labels.Count == 0) { Labels = emptyStringDict; } else { Labels.IsReadOnly = true; } } if ((options & EntityQueryOptions.FetchAliases) == EntityQueryOptions.FetchAliases) { Aliases = serializable.Aliases; if (Aliases.Count == 0) { Aliases = emptyStringsDict; } else { Aliases.IsReadOnly = true; } } if ((options & EntityQueryOptions.FetchDescriptions) == EntityQueryOptions.FetchDescriptions) { Descriptions = serializable.Descriptions; if (Descriptions.Count == 0) { Descriptions = emptyStringDict; } else { Descriptions.IsReadOnly = true; } } if ((options & EntityQueryOptions.FetchSiteLinks) == EntityQueryOptions.FetchSiteLinks) { SiteLinks = serializable.SiteLinks; if (SiteLinks.Count == 0) { SiteLinks = emptySiteLinks; } else { SiteLinks.IsReadOnly = true; } } if ((options & EntityQueryOptions.FetchClaims) == EntityQueryOptions.FetchClaims) { Claims = serializable.Claims; if (Claims.Count == 0) { Claims = emptyClaims; } else { Claims.IsReadOnly = true; } } }