From a3e92be23fc0aa204949420b76c05035f3172398 Mon Sep 17 00:00:00 2001 From: Azure Gem Date: Sun, 30 Aug 2026 17:03:07 -0400 Subject: [PATCH 1/2] Cancellation Tokens --- NetStone.Test/Tests.cs | 4 +- NetStone/Definitions/DefinitionsContainer.cs | 6 +- .../Definitions/XivApiDefinitionsContainer.cs | 52 +++--- NetStone/LodestoneClient.cs | 155 ++++++++++++------ NetStone/Model/IPaginatedResult.cs | 18 +- NetStone/NetStone.xml | 64 +++++--- 6 files changed, 188 insertions(+), 111 deletions(-) diff --git a/NetStone.Test/Tests.cs b/NetStone.Test/Tests.cs index 4455b48..bf41fec 100644 --- a/NetStone.Test/Tests.cs +++ b/NetStone.Test/Tests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -210,7 +210,7 @@ public async Task CheckFreeCompanyRecruiting() //Focus Assert.AreEqual("Always", fc.ActiveState); - Assert.AreEqual("Open", fc.Recruitment); + Assert.AreEqual("Closed", fc.Recruitment); Assert.IsNotNull(fc.Focus); Assert.AreEqual("Role-playing", fc.Focus.RolePlay.Name); diff --git a/NetStone/Definitions/DefinitionsContainer.cs b/NetStone/Definitions/DefinitionsContainer.cs index b8254e0..31abd3d 100644 --- a/NetStone/Definitions/DefinitionsContainer.cs +++ b/NetStone/Definitions/DefinitionsContainer.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Threading; using System.Threading.Tasks; using NetStone.Definitions.Model; using NetStone.Definitions.Model.Character; @@ -131,8 +132,9 @@ public abstract class DefinitionsContainer : IDisposable /// /// Loads the definitions from repo /// + /// The cancellation token to cancel operation /// Reload task - public abstract Task Reload(); + public abstract Task Reload(CancellationToken cancellationToken = default); /// public abstract void Dispose(); diff --git a/NetStone/Definitions/XivApiDefinitionsContainer.cs b/NetStone/Definitions/XivApiDefinitionsContainer.cs index 0194e22..be83a01 100644 --- a/NetStone/Definitions/XivApiDefinitionsContainer.cs +++ b/NetStone/Definitions/XivApiDefinitionsContainer.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using NetStone.Definitions.Model; using NetStone.Definitions.Model.Character; @@ -33,45 +34,46 @@ public XivApiDefinitionsContainer() /// /// Fetches current CSS selector definitions from xivapi/lodestone-css-selectors github repository. /// + /// The cancellation token to cancel operation /// /// /// Task for this operation - public override async Task Reload() + public override async Task Reload(CancellationToken cancellationToken = default) { - this.Meta = await GetDefinition("meta.json"); + this.Meta = await GetDefinition("meta.json", cancellationToken); - this.Character = await GetDefinition("profile/character.json"); - this.ClassJob = await GetDefinition("profile/classjob.json"); - this.Gear = await GetDefinition("profile/gearset.json"); - this.GearEntry = await GetDefinition("profile/gearentry.json"); - this.SoulCrystalEntry = await GetDefinition("profile/soulcrystal.json"); - this.Attributes = await GetDefinition("profile/attributes.json"); - this.Achievement = await GetDefinition("profile/achievements.json"); - this.Mount = await GetDefinition("profile/mount.json"); - this.Minion = await GetDefinition("profile/minion.json"); + this.Character = await GetDefinition("profile/character.json", cancellationToken); + this.ClassJob = await GetDefinition("profile/classjob.json", cancellationToken); + this.Gear = await GetDefinition("profile/gearset.json", cancellationToken); + this.GearEntry = await GetDefinition("profile/gearentry.json", cancellationToken); + this.SoulCrystalEntry = await GetDefinition("profile/soulcrystal.json", cancellationToken); + this.Attributes = await GetDefinition("profile/attributes.json", cancellationToken); + this.Achievement = await GetDefinition("profile/achievements.json", cancellationToken); + this.Mount = await GetDefinition("profile/mount.json", cancellationToken); + this.Minion = await GetDefinition("profile/minion.json", cancellationToken); - this.FreeCompany = await GetDefinition("freecompany/freecompany.json"); - this.FreeCompanyFocus = await GetDefinition("freecompany/focus.json"); + this.FreeCompany = await GetDefinition("freecompany/freecompany.json", cancellationToken); + this.FreeCompanyFocus = await GetDefinition("freecompany/focus.json", cancellationToken); this.FreeCompanyReputation = - await GetDefinition("freecompany/reputation.json"); + await GetDefinition("freecompany/reputation.json", cancellationToken); - this.FreeCompanyMembers = await GetDefinition>("freecompany/members.json"); + this.FreeCompanyMembers = await GetDefinition>("freecompany/members.json", cancellationToken); - this.CharacterSearch = await GetDefinition>("search/character.json"); - this.FreeCompanySearch = await GetDefinition>("search/freecompany.json"); + this.CharacterSearch = await GetDefinition>("search/character.json", cancellationToken); + this.FreeCompanySearch = await GetDefinition>("search/freecompany.json", cancellationToken); - this.CrossworldLinkshell = await GetDefinition("cwls/cwls.json"); - this.CrossworldLinkshellMember = await GetDefinition>("cwls/members.json"); - this.CrossworldLinkshellSearch = await GetDefinition>("search/cwls.json"); + this.CrossworldLinkshell = await GetDefinition("cwls/cwls.json", cancellationToken); + this.CrossworldLinkshellMember = await GetDefinition>("cwls/members.json", cancellationToken); + this.CrossworldLinkshellSearch = await GetDefinition>("search/cwls.json", cancellationToken); - this.Linkshell = await GetDefinition("linkshell/ls.json"); - this.LinkshellMember = await GetDefinition>("linkshell/members.json"); - this.LinkshellSearch = await GetDefinition>("search/linkshell.json"); + this.Linkshell = await GetDefinition("linkshell/ls.json", cancellationToken); + this.LinkshellMember = await GetDefinition>("linkshell/members.json", cancellationToken); + this.LinkshellSearch = await GetDefinition>("search/linkshell.json", cancellationToken); } - private async Task GetDefinition(string path) where T : IDefinition + private async Task GetDefinition(string path, CancellationToken cancellationToken) where T : IDefinition { var json = await this.client.GetStringAsync(path); var result = JsonConvert.DeserializeObject(json); diff --git a/NetStone/LodestoneClient.cs b/NetStone/LodestoneClient.cs index 849e68f..32aa1c5 100644 --- a/NetStone/LodestoneClient.cs +++ b/NetStone/LodestoneClient.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using AngleSharp; using AngleSharp.Dom; @@ -79,14 +80,23 @@ public static LodestoneClient GetCustomClient(DefinitionsContainer definitions,I /// /// Service providing game data for parsing /// Base address for Lodestone access (defaults to EU Lodestone) + /// The cancellation token to cancel operation /// /// /// public static async Task GetClientAsync(IGameDataProvider? gameData = null, - string lodestoneBaseAddress = Constants.LodestoneBase) + string lodestoneBaseAddress = Constants.LodestoneBase, CancellationToken cancellationToken = default) { var definitions = new XivApiDefinitionsContainer(); - await definitions.Reload(); + try + { + await definitions.Reload(cancellationToken); + } + catch + { + definitions.Dispose(); + throw; + } return new LodestoneClient(definitions, gameData, lodestoneBaseAddress); } @@ -97,73 +107,89 @@ public static async Task GetClientAsync(IGameDataProvider? game /// Get a character by its Lodestone ID. /// /// The ID of the character. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about the character. - public async Task GetCharacter(string id) + public async Task GetCharacter(string id, CancellationToken cancellationToken = default) { var character = await GetParsed($"/lodestone/character/{id}/", node => new LodestoneCharacter( - this, node, this.Definitions, id)); + this, node, this.Definitions, id), + cancellationToken: cancellationToken); if (character == null) return null; character.Gear.Mainhand = character.Gear.MainHandLink != null ? await GetParsed(character.Gear.MainHandLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Offhand = character.Gear.OffHandLink != null ? await GetParsed(character.Gear.OffHandLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Head = character.Gear.HeadLink != null ? await GetParsed(character.Gear.HeadLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Body = character.Gear.BodyLink != null ? await GetParsed(character.Gear.BodyLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Hands = character.Gear.HandsLink != null ? await GetParsed(character.Gear.HandsLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; #pragma warning disable CS0618 // Type or member is obsolete character.Gear.Waist = character.Gear.WaistLink != null ? await GetParsed(character.Gear.WaistLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; #pragma warning restore CS0618 // Type or member is obsolete character.Gear.Legs = character.Gear.LegsLink != null ? await GetParsed(character.Gear.LegsLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Feet = character.Gear.FeetLink != null ? await GetParsed(character.Gear.FeetLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Earrings = character.Gear.EarringsLink != null ? await GetParsed(character.Gear.EarringsLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Necklace = character.Gear.NecklaceLink != null ? await GetParsed(character.Gear.NecklaceLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Bracelets = character.Gear.BraceletsLink != null ? await GetParsed(character.Gear.BraceletsLink, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Ring1 = character.Gear.Ring1Link != null ? await GetParsed(character.Gear.Ring1Link, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Ring2 = character.Gear.Ring2Link != null ? await GetParsed(character.Gear.Ring2Link, - node => new GearEntry(this, node, this.Definitions.GearEntry)) + node => new GearEntry(this, node, this.Definitions.GearEntry), + cancellationToken: cancellationToken) : null; character.Gear.Soulcrystal = character.Gear.SoulcrystalLink != null ? await GetParsed(character.Gear.SoulcrystalLink, - node => new SoulcrystalEntry(node, this.Definitions.SoulCrystalEntry)) + node => new SoulcrystalEntry(node, this.Definitions.SoulCrystalEntry), + cancellationToken: cancellationToken) : null; return character; @@ -174,10 +200,14 @@ public static async Task GetClientAsync(IGameDataProvider? game /// You can also get this from the character directly by calling . /// /// The ID of the character. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about the characters' classes and jobs. - public async Task GetCharacterClassJob(string id) => await GetParsed( - $"/lodestone/character/{id}/class_job/", node => new CharacterClassJob(node, this.Definitions.ClassJob)); + public async Task GetCharacterClassJob(string id, CancellationToken cancellationToken = default) => + await GetParsed( + $"/lodestone/character/{id}/class_job/", + node => new CharacterClassJob(node, this.Definitions.ClassJob), + cancellationToken: cancellationToken); /// /// Get a characters' unlocked achievement information by its Lodestone ID. @@ -185,95 +215,110 @@ public static async Task GetClientAsync(IGameDataProvider? game /// /// The ID of the character. /// The number of the page that should be fetched. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about the characters' achievements. - public async Task GetCharacterAchievement(string id, int page = 1) => + public async Task GetCharacterAchievement(string id, int page = 1, CancellationToken cancellationToken = default) => await GetParsed( $"/lodestone/character/{id}/achievement/?page={page}", - node => new CharacterAchievementPage(this, node, this.Definitions.Achievement, id)); + node => new CharacterAchievementPage(this, node, this.Definitions.Achievement, id), + cancellationToken: cancellationToken); /// /// Get a characters' unlocked mount information by its Lodestone ID. /// You can also get this from the character directly by calling . /// /// The ID of the character. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about the characters' mounts. - public async Task GetCharacterMount(string id) => await GetParsed( + public async Task GetCharacterMount(string id, CancellationToken cancellationToken = default) => await GetParsed( $"/lodestone/character/{id}/mount/", node => new CharacterCollectable(node, this.Definitions.Mount), - UserAgent.Mobile); + UserAgent.Mobile, cancellationToken); /// /// Get a characters' unlocked minion information by its Lodestone ID. /// You can also get this from the character directly by calling . /// /// The ID of the character. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about the characters' minions. - public async Task GetCharacterMinion(string id) => await GetParsed( + public async Task GetCharacterMinion(string id, CancellationToken cancellationToken = default) => await GetParsed( $"/lodestone/character/{id}/minion/", node => new CharacterCollectable(node, this.Definitions.Minion), - UserAgent.Mobile); + UserAgent.Mobile, + cancellationToken); /// /// Search lodestone for a character with the specified query. /// /// object detailing search parameters /// The page of search results to fetch. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// containing search results. - public async Task SearchCharacter(CharacterSearchQuery query, int page = 1) => + public async Task SearchCharacter(CharacterSearchQuery query, int page = 1, CancellationToken cancellationToken = default) => await GetParsed($"/lodestone/character/{query.BuildQueryString()}&page={page}", - node => new CharacterSearchPage(this, node, this.Definitions.CharacterSearch, query)); - + node => new CharacterSearchPage(this, node, this.Definitions.CharacterSearch, query), + cancellationToken: cancellationToken); + #endregion - + #region Linkshells /// /// Gets a cross world link shell by its id. /// /// The ID of the cross world linkshell. /// + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about the cross world link shell - public async Task GetCrossworldLinkshell(string id, int page = 1) => + public async Task GetCrossworldLinkshell(string id, int page = 1, CancellationToken cancellationToken = default) => await GetParsed($"/lodestone/crossworld_linkshell/{id}?page={page}", - node => new LodestoneCrossworldLinkshell(this, node, this.Definitions,id)); - + node => new LodestoneCrossworldLinkshell(this, node, this.Definitions,id), + cancellationToken: cancellationToken); + /// /// Search lodestone for a character with the specified query. /// /// object detailing search parameters /// The page of search results to fetch. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// containing search results. - public async Task SearchCrossworldLinkshell(CrossworldLinkshellSearchQuery query, int page = 1) => + public async Task SearchCrossworldLinkshell(CrossworldLinkshellSearchQuery query, int page = 1, CancellationToken cancellationToken = default) => await GetParsed($"/lodestone/crossworld_linkshell/{query.BuildQueryString()}&page={page}", - node => new CrossworldLinkshellSearchPage(this, node, this.Definitions.CrossworldLinkshellSearch, query)); - + node => new CrossworldLinkshellSearchPage(this, node, this.Definitions.CrossworldLinkshellSearch, query), + cancellationToken: cancellationToken); + /// /// Gets a link shell by its id. /// /// The ID of the linkshell. /// + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about the cross world link shell - public async Task GetLinkshell(string id, int page = 1) => + public async Task GetLinkshell(string id, int page = 1, CancellationToken cancellationToken = default) => await GetParsed($"/lodestone/linkshell/{id}?page={page}", - node => new LodestoneLinkshell(this, node, this.Definitions,id)); - + node => new LodestoneLinkshell(this, node, this.Definitions,id), + cancellationToken: cancellationToken); + /// /// Search lodestone for a linkshell with the specified query. /// /// object detailing search parameters /// The page of search results to fetch. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// containing search results. - public async Task SearchLinkshell(LinkshellSearchQuery query, int page = 1) => + public async Task SearchLinkshell(LinkshellSearchQuery query, int page = 1, CancellationToken cancellationToken = default) => await GetParsed($"/lodestone/linkshell/{query.BuildQueryString()}&page={page}", - node => new LinkshellSearchPage(this, node, this.Definitions.LinkshellSearch, query)); - + node => new LinkshellSearchPage(this, node, this.Definitions.LinkshellSearch, query), + cancellationToken: cancellationToken); + #endregion #region FreeCompany @@ -282,32 +327,39 @@ await GetParsed($"/lodestone/linkshell/{query.BuildQueryString()}&page={page}", /// Get a character by its Lodestone ID. /// /// The ID of the character. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about the character. - public async Task GetFreeCompany(string id) => await GetParsed( - $"/lodestone/freecompany/{id}/", node => new LodestoneFreeCompany(this, node, this.Definitions, id)); + public async Task GetFreeCompany(string id, CancellationToken cancellationToken = default) => await GetParsed( + $"/lodestone/freecompany/{id}/", + node => new LodestoneFreeCompany(this, node, this.Definitions, id), + cancellationToken: cancellationToken); /// /// Get the members of a Free Company /// /// The ID of the free company. /// The page of members to fetch. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// class containing information about FC members. - public async Task GetFreeCompanyMembers(string id, int page = 1) => await GetParsed( + public async Task GetFreeCompanyMembers(string id, int page = 1, CancellationToken cancellationToken = default) => await GetParsed( $"/lodestone/freecompany/{id}/member/?page={page}", - node => new FreeCompanyMembers(this, node, this.Definitions.FreeCompanyMembers, id)); + node => new FreeCompanyMembers(this, node, this.Definitions.FreeCompanyMembers, id), + cancellationToken: cancellationToken); /// /// Search lodestone for a free company with the specified query. /// /// object detailing search parameters. /// The page of search results to fetch. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// containing search results. - public async Task SearchFreeCompany(FreeCompanySearchQuery query, int page = 1) => + public async Task SearchFreeCompany(FreeCompanySearchQuery query, int page = 1, CancellationToken cancellationToken = default) => await GetParsed($"/lodestone/freecompany/{query.BuildQueryString()}&page={page}", - node => new FreeCompanySearchPage(this, node, this.Definitions.FreeCompanySearch, query)); + node => new FreeCompanySearchPage(this, node, this.Definitions.FreeCompanySearch, query), + cancellationToken: cancellationToken); #endregion @@ -318,10 +370,11 @@ await GetParsed($"/lodestone/freecompany/{query.BuildQueryString()}&page={page}" /// The URL to fetch. /// Func creating the LodestoneParseable. /// The user agent to use for the request. + /// The cancellation token to cancel operation /// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. /// The instantiated LodestoneParseable in case of success. private async Task GetParsed(string url, Func createParseable, - UserAgent agent = UserAgent.Desktop) where T : LodestoneParseable + UserAgent agent = UserAgent.Desktop, CancellationToken cancellationToken = default) where T : LodestoneParseable { var config = Configuration.Default.WithDefaultLoader(); var context = BrowsingContext.New(config); @@ -339,7 +392,7 @@ await GetParsed($"/lodestone/freecompany/{query.BuildQueryString()}&page={page}" throw new ArgumentOutOfRangeException(nameof(agent), agent, null); } - var response = await this.client.SendAsync(request); + var response = await this.client.SendAsync(request, cancellationToken); if (response.StatusCode == HttpStatusCode.NotFound) return null; diff --git a/NetStone/Model/IPaginatedResult.cs b/NetStone/Model/IPaginatedResult.cs index b002410..32daa89 100644 --- a/NetStone/Model/IPaginatedResult.cs +++ b/NetStone/Model/IPaginatedResult.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using AngleSharp.Dom; using NetStone.Definitions.Model; @@ -26,8 +27,9 @@ public interface IPaginatedResult where T : LodestoneParseable /// /// Gets the next page of results /// + /// The cancellation token to cancel operation /// Task of retrieving next page - Task GetNextPage(); + Task GetNextPage(CancellationToken cancellationToken = default); } /// @@ -40,7 +42,7 @@ public abstract class PaginatedIdResult { /// protected PaginatedIdResult(IElement rootNode, PagedDefinition pageDefinition, - Func> nextPageFunc, string id) + Func> nextPageFunc, string id) : base(rootNode, pageDefinition, nextPageFunc, id) { } @@ -56,7 +58,7 @@ public abstract class PaginatedSearchResult { /// protected PaginatedSearchResult(IElement rootNode, PagedDefinition pageDefinition, - Func> nextPageFunc, + Func> nextPageFunc, TQuery query) : base(rootNode, pageDefinition, nextPageFunc, query) { @@ -76,7 +78,7 @@ public abstract class PaginatedResult : Lodes private readonly TRequest request; - private readonly Func> nextPageFunc; + private readonly Func> nextPageFunc; /// /// @@ -85,7 +87,7 @@ public abstract class PaginatedResult : Lodes /// CSS definitions for the paginated type /// Function to retrieve a page of this type /// The input used to request further pages. - protected PaginatedResult(IElement rootNode, PagedDefinition pageDefinition,Func> nextPageFunc, TRequest request) : base(rootNode) + protected PaginatedResult(IElement rootNode, PagedDefinition pageDefinition,Func> nextPageFunc, TRequest request) : base(rootNode) { this.PageDefinition = pageDefinition; this.request = request; @@ -157,7 +159,7 @@ private void ParsePagesCount() } /// - public async Task GetNextPage() + public async Task GetNextPage(CancellationToken cancellationToken = default) { if (!this.HasResults) return null; @@ -165,6 +167,6 @@ private void ParsePagesCount() if (this.CurrentPage == this.NumPages) return null; - return await this.nextPageFunc(this.request, this.CurrentPage + 1); + return await this.nextPageFunc(this.request, this.CurrentPage + 1, cancellationToken); } } \ No newline at end of file diff --git a/NetStone/NetStone.xml b/NetStone/NetStone.xml index 2e877e8..e9851be 100644 --- a/NetStone/NetStone.xml +++ b/NetStone/NetStone.xml @@ -130,10 +130,11 @@ Definitions for link-shell searches - + Loads the definitions from repo + The cancellation token to cancel operation Reload task @@ -1659,10 +1660,11 @@ Constructs this class without populating definitions - + Fetches current CSS selector definitions from xivapi/lodestone-css-selectors github repository. + The cancellation token to cancel operation Task for this operation @@ -1795,140 +1797,154 @@ Initializes and returns a new Lodestone client with custom definitions provided
- To get a client with standard definitions use + To get a client with standard definitions use
A set of custom definitions Service providing game data for parsing Base address for Lodestone access (defaults to EU Lodestone)
- + Initializes and returns a new Lodestone client with current definitions loaded from xivapi/lodestone-css-selectors Service providing game data for parsing Base address for Lodestone access (defaults to EU Lodestone) + The cancellation token to cancel operation - + Get a character by its Lodestone ID. The ID of the character. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about the character. - + Get a characters' class/job information by its Lodestone ID. You can also get this from the character directly by calling . The ID of the character. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about the characters' classes and jobs. - + Get a characters' unlocked achievement information by its Lodestone ID. You can also get this from the character directly by calling . The ID of the character. The number of the page that should be fetched. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about the characters' achievements. - + Get a characters' unlocked mount information by its Lodestone ID. You can also get this from the character directly by calling . The ID of the character. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about the characters' mounts. - + Get a characters' unlocked minion information by its Lodestone ID. You can also get this from the character directly by calling . The ID of the character. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about the characters' minions. - + Search lodestone for a character with the specified query. object detailing search parameters The page of search results to fetch. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. containing search results. - + Gets a cross world link shell by its id. The ID of the cross world linkshell. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about the cross world link shell - + Search lodestone for a character with the specified query. object detailing search parameters The page of search results to fetch. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. containing search results. - + Gets a link shell by its id. The ID of the linkshell. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about the cross world link shell - + Search lodestone for a linkshell with the specified query. object detailing search parameters The page of search results to fetch. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. containing search results. - + Get a character by its Lodestone ID. The ID of the character. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about the character. - + Get the members of a Free Company The ID of the free company. The page of members to fetch. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. class containing information about FC members. - + Search lodestone for a free company with the specified query. object detailing search parameters. The page of search results to fetch. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. containing search results. - + Get the instantiated LodestoneParseable if the request succeeded, or null in case of StatusCode.NotFound. @@ -1936,6 +1952,7 @@ The URL to fetch. Func creating the LodestoneParseable. The user agent to use for the request. + The cancellation token to cancel operation The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout. The instantiated LodestoneParseable in case of success. @@ -1980,10 +1997,11 @@ Total number of pages - + Gets the next page of results + The cancellation token to cancel operation Task of retrieving next page @@ -1991,7 +2009,7 @@ Container class holding paginated information - + @@ -1999,7 +2017,7 @@ Container class holding paginated information - + @@ -2012,7 +2030,7 @@ Definition for the paginated type - + @@ -2042,7 +2060,7 @@ - + From 0a999d5d33a4fe6415776d8563cb3605a866c270 Mon Sep 17 00:00:00 2001 From: Azure Gem Date: Sun, 30 Aug 2026 17:29:51 -0400 Subject: [PATCH 2/2] missed one --- NetStone/Definitions/XivApiDefinitionsContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NetStone/Definitions/XivApiDefinitionsContainer.cs b/NetStone/Definitions/XivApiDefinitionsContainer.cs index be83a01..ac9e041 100644 --- a/NetStone/Definitions/XivApiDefinitionsContainer.cs +++ b/NetStone/Definitions/XivApiDefinitionsContainer.cs @@ -75,7 +75,9 @@ public override async Task Reload(CancellationToken cancellationToken = default) private async Task GetDefinition(string path, CancellationToken cancellationToken) where T : IDefinition { - var json = await this.client.GetStringAsync(path); + var response = await this.client.GetAsync(path, cancellationToken); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject(json); return result == null ? throw new FormatException($"Could not parse definitions in {path}.") : result; }