diff --git a/osu.Game.Rulesets.Karaoke.Tests/Overlays/TestSceneKaraokeChangeLogMarkdownContainer.cs b/osu.Game.Rulesets.Karaoke.Tests/Overlays/TestSceneKaraokeChangeLogMarkdownContainer.cs index 94d3dd396..518d6b764 100644 --- a/osu.Game.Rulesets.Karaoke.Tests/Overlays/TestSceneKaraokeChangeLogMarkdownContainer.cs +++ b/osu.Game.Rulesets.Karaoke.Tests/Overlays/TestSceneKaraokeChangeLogMarkdownContainer.cs @@ -24,10 +24,11 @@ public partial class TestSceneKaraokeChangeLogMarkdownContainer : OsuTestScene [SetUp] public void SetUp() => Schedule(() => { - var build = new APIChangelogBuild("karaoke-dev", "karaoke-dev.github.io") + var build = new APIChangelogBuild { - Path = "content/changelog/2020.0620", + DocumentUrl = "https://raw.githubusercontent.com/karaoke-dev/karaoke-dev.github.io/master/content/changelog/2020.0620", RootUrl = "https://github.com/karaoke-dev/karaoke-dev.github.io/tree/master/content/changelog/2020.0620", + Content = "---\ntitle: \"2020.0620\"\ndate: 2020-06-20\n---\n\n## Achievement\n\n- Might support convert `UTAU`/`SynthV` project into karaoke beatmap in future. [karaoke](#100#101@andy840119)", }; Children = new Drawable[] diff --git a/osu.Game.Rulesets.Karaoke/Online/API/Requests/GetChangelogBuildRequest.cs b/osu.Game.Rulesets.Karaoke/Online/API/Requests/GetChangelogBuildRequest.cs new file mode 100644 index 000000000..b8d495959 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Online/API/Requests/GetChangelogBuildRequest.cs @@ -0,0 +1,24 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using Octokit; +using osu.Game.Rulesets.Karaoke.Online.API.Requests.Responses; + +namespace osu.Game.Rulesets.Karaoke.Online.API.Requests; + +public class GetChangelogBuildRequest : GithubChangeLogAPIRequest +{ + private readonly APIChangelogBuild originBuild; + + public GetChangelogBuildRequest(APIChangelogBuild originBuild) + { + this.originBuild = originBuild; + } + + protected override async Task Perform(IGitHubClient client) + { + string contentString = await GetChangelogContent(client, originBuild.Version).ConfigureAwait(false); + return CreateBuildWithContent(originBuild, contentString); + } +} diff --git a/osu.Game.Rulesets.Karaoke/Online/API/Requests/GetChangelogRequest.cs b/osu.Game.Rulesets.Karaoke/Online/API/Requests/GetChangelogRequest.cs new file mode 100644 index 000000000..5c41fff43 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Online/API/Requests/GetChangelogRequest.cs @@ -0,0 +1,92 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Octokit; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Karaoke.Extensions; +using osu.Game.Rulesets.Karaoke.Online.API.Requests.Responses; + +namespace osu.Game.Rulesets.Karaoke.Online.API.Requests; + +public class GetChangelogRequest : GithubChangeLogAPIRequest +{ + protected override async Task Perform(IGitHubClient client) + { + var builds = await getAllBuilds(client).ConfigureAwait(false); + var previewBuilds = (await Task.WhenAll(builds.Take(5).Select(x => createPreviewBuild(client, x))).ConfigureAwait(false)).ToList(); + int[] years = generateYears(builds); + + return new APIChangelogIndex + { + Years = years, + Builds = builds, + PreviewBuilds = previewBuilds, + }; + } + + private static async Task> getAllBuilds(IGitHubClient client) + { + var reposAscending = await client + .Repository + .Content + .GetAllContents(ORGANIZATION_NAME, PROJECT_NAME, CHANGELOG_PATH) + .ConfigureAwait(false); + + var builds = reposAscending + .Reverse() + .Where(x => x.Type == ContentType.Dir) + .Select(createBuild) + .ToList(); + + // adjust the mapping of previous/next versions by hand. + foreach (var build in builds) + { + build.Versions.Previous = builds.GetPrevious(build); + build.Versions.Next = builds.GetNext(build); + } + + return builds; + } + + private static APIChangelogBuild createBuild(RepositoryContent content) + { + return new APIChangelogBuild + { + DocumentUrl = getDocumentUrl(content.Path), + RootUrl = content.HtmlUrl, + Version = content.Name, + PublishedAt = getPublishDateFromName(content.Name), + }; + + static DateTimeOffset getPublishDateFromName(string name) + { + var regex = new Regex("(?[-0-9]+).(?[-0-9]{2})(?[-0-9]{2})"); + var result = regex.Match(name); + if (!result.Success) + return DateTimeOffset.MaxValue; + + int year = result.GetGroupValue("year"); + int month = result.GetGroupValue("month"); + int day = result.GetGroupValue("day"); + + return new DateTimeOffset(new DateTime(year, month, day)); + } + + string getDocumentUrl(string path) + => $"https://raw.githubusercontent.com/{ORGANIZATION_NAME}/{PROJECT_NAME}/{BRANCH_NAME}/{path}/"; + } + + private static async Task createPreviewBuild(IGitHubClient client, APIChangelogBuild originBuild) + { + string contentString = await GetChangelogContent(client, originBuild.Version).ConfigureAwait(false); + return CreateBuildWithContent(originBuild, contentString); + } + + private static int[] generateYears(IEnumerable builds) + => builds.Select(x => x.PublishedAt.Year).Distinct().ToArray(); +} diff --git a/osu.Game.Rulesets.Karaoke/Online/API/Requests/GithubAPIRequest.cs b/osu.Game.Rulesets.Karaoke/Online/API/Requests/GithubAPIRequest.cs new file mode 100644 index 000000000..536e2cdf9 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Online/API/Requests/GithubAPIRequest.cs @@ -0,0 +1,84 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Threading.Tasks; +using Octokit; +using osu.Game.Online.API; + +namespace osu.Game.Rulesets.Karaoke.Online.API.Requests; + +public abstract class GithubAPIRequest where T : class +{ + protected virtual GitHubClient CreateGitHubClient() => new(new ProductHeaderValue(organizationName)); + + /// + /// Invoked on successful completion of an API request. + /// This will be scheduled to the API's internal scheduler (run on update thread automatically). + /// + public event APISuccessHandler? Success; + + /// + /// Invoked on failure to complete an API request. + /// This will be scheduled to the API's internal scheduler (run on update thread automatically). + /// + public event APIFailureHandler? Failure; + + private readonly object completionStateLock = new(); + + /// + /// The state of this request, from an outside perspective. + /// This is used to ensure correct notification events are fired. + /// + public APIRequestCompletionState CompletionState { get; private set; } + + private readonly string organizationName; + + protected GithubAPIRequest(string organizationName) + { + this.organizationName = organizationName; + } + + public async Task Perform() + { + var client = CreateGitHubClient(); + + try + { + var response = await Perform(client).ConfigureAwait(false); + triggerSuccess(response); + } + catch (Exception e) + { + triggerFailure(e); + } + } + + protected abstract Task Perform(IGitHubClient client); + + private void triggerSuccess(T response) + { + lock (completionStateLock) + { + if (CompletionState != APIRequestCompletionState.Waiting) + return; + + CompletionState = APIRequestCompletionState.Completed; + } + + Success?.Invoke(response); + } + + private void triggerFailure(Exception e) + { + lock (completionStateLock) + { + if (CompletionState != APIRequestCompletionState.Waiting) + return; + + CompletionState = APIRequestCompletionState.Failed; + } + + Failure?.Invoke(e); + } +} diff --git a/osu.Game.Rulesets.Karaoke/Online/API/Requests/GithubChangeLogAPIRequest.cs b/osu.Game.Rulesets.Karaoke/Online/API/Requests/GithubChangeLogAPIRequest.cs new file mode 100644 index 000000000..a56b7f0ba --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Online/API/Requests/GithubChangeLogAPIRequest.cs @@ -0,0 +1,51 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using Octokit; +using osu.Game.Rulesets.Karaoke.Online.API.Requests.Responses; + +namespace osu.Game.Rulesets.Karaoke.Online.API.Requests; + +public abstract class GithubChangeLogAPIRequest : GithubAPIRequest where T : class +{ + protected const string ORGANIZATION_NAME = "karaoke-dev"; + protected const string PROJECT_NAME = $"{ORGANIZATION_NAME}.github.io"; + protected const string BRANCH_NAME = "master"; + protected const string CHANGELOG_PATH = "content/changelog"; + + protected GithubChangeLogAPIRequest() + : base(ORGANIZATION_NAME) + { + } + + protected static async Task GetChangelogContent(IGitHubClient client, string version) + { + string changeLogPath = $"{CHANGELOG_PATH}/{version}/index.md"; + byte[]? content = await client + .Repository + .Content + .GetRawContent(ORGANIZATION_NAME, PROJECT_NAME, changeLogPath) + .ConfigureAwait(false); + + // convert the content to a string + return System.Text.Encoding.UTF8.GetString(content); + } + + protected static APIChangelogBuild CreateBuildWithContent(APIChangelogBuild originBuild, string content) + { + return new APIChangelogBuild + { + DocumentUrl = originBuild.DocumentUrl, + RootUrl = originBuild.RootUrl, + Version = originBuild.Version, + Content = content, + Versions = + { + Previous = originBuild.Versions.Previous, + Next = originBuild.Versions.Next, + }, + PublishedAt = originBuild.PublishedAt, + }; + } +} diff --git a/osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogBuild.cs b/osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogBuild.cs index 1ade120a1..9773c4a71 100644 --- a/osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogBuild.cs +++ b/osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogBuild.cs @@ -7,39 +7,10 @@ namespace osu.Game.Rulesets.Karaoke.Online.API.Requests.Responses; public class APIChangelogBuild { - /// - /// - /// - /// Account or organization name - /// Project name - /// Branch name - public APIChangelogBuild(string organization, string project, string branch = "master") - { - OrganizationName = organization; - ProjectName = project; - Branch = branch; - Versions = new VersionNavigation(); - } - - /// - /// Organization name - /// - public string OrganizationName { get; } - - /// - /// Project name - /// - public string ProjectName { get; } - - /// - /// Branch name - /// - public string Branch { get; } - /// /// The URL of the loaded document. /// - public string DocumentUrl => $"https://raw.githubusercontent.com/{OrganizationName}/{ProjectName}/{Branch}/{Path}/"; + public string DocumentUrl { get; set; } = null!; /// /// The base URL for all root-relative links. @@ -47,24 +18,27 @@ public APIChangelogBuild(string organization, string project, string branch = "m public string RootUrl { get; set; } = null!; /// - /// Path of the project + /// Version number /// - public string Path { get; set; } = null!; + /// 2023.0123 + /// 2023.1111 + public string Version { get; set; } = null!; /// - /// Path to download readme url + /// Display version /// - public string ReadmeDownloadUrl => $"{DocumentUrl}index.md"; + public string DisplayVersion => Version; /// - /// Display version + /// Might be preview or detail markdown content. + /// And the content is markdown format. /// - public string DisplayVersion { get; set; } = null!; + public string? Content { get; set; } /// /// Version /// - public VersionNavigation Versions { get; } + public VersionNavigation Versions { get; } = new(); /// /// Created date. @@ -83,4 +57,6 @@ public class VersionNavigation /// public APIChangelogBuild? Previous { get; set; } } + + public override string ToString() => $"Karaoke! {DisplayVersion}"; } diff --git a/osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogSidebar.cs b/osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogIndex.cs similarity index 66% rename from osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogSidebar.cs rename to osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogIndex.cs index 576270db6..bec2284f5 100644 --- a/osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogSidebar.cs +++ b/osu.Game.Rulesets.Karaoke/Online/API/Requests/Responses/APIChangelogIndex.cs @@ -6,9 +6,11 @@ namespace osu.Game.Rulesets.Karaoke.Online.API.Requests.Responses; -public class APIChangelogSidebar +public class APIChangelogIndex { - public IEnumerable Changelogs { get; set; } = Array.Empty(); + public List Builds { get; set; } = new(); + + public List PreviewBuilds { get; set; } = new(); public int[] Years { get; set; } = Array.Empty(); } diff --git a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangeLogMarkdownContainer.cs b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangeLogMarkdownContainer.cs index 299db5051..66d094fab 100644 --- a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangeLogMarkdownContainer.cs +++ b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangeLogMarkdownContainer.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Text.RegularExpressions; using Markdig.Syntax.Inlines; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Layout; @@ -25,17 +23,7 @@ public ChangeLogMarkdownContainer(APIChangelogBuild build) { DocumentUrl = build.DocumentUrl; RootUrl = build.RootUrl; - - using var httpClient = new HttpClient(); - - try - { - Text = httpClient.GetStringAsync(build.ReadmeDownloadUrl).GetResultSafely(); - } - catch (Exception) - { - Text = "Oops, seems there's something wrong with network."; - } + Text = build.Content; } public override OsuMarkdownTextFlowContainer CreateTextFlow() => new ChangeLogMarkdownTextFlowContainer(); diff --git a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangelogListing.cs b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangelogListing.cs index a59f2208e..02d209c05 100644 --- a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangelogListing.cs +++ b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangelogListing.cs @@ -20,9 +20,9 @@ public partial class ChangelogListing : ChangelogContent { private readonly IReadOnlyList entries; - public ChangelogListing(IEnumerable entries) + public ChangelogListing(List entries) { - this.entries = entries.Take(4).ToList(); + this.entries = entries; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangelogSingleBuild.cs index 8e045c2ee..c4cdef3d7 100644 --- a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/ChangelogSingleBuild.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Rulesets.Karaoke.Online.API.Requests; using osu.Game.Rulesets.Karaoke.Online.API.Requests.Responses; namespace osu.Game.Rulesets.Karaoke.Overlays.Changelog; @@ -20,32 +22,47 @@ namespace osu.Game.Rulesets.Karaoke.Overlays.Changelog; /// public partial class ChangelogSingleBuild : ChangelogContent { - private readonly APIChangelogBuild build; + private readonly APIChangelogBuild originBuild; public ChangelogSingleBuild(APIChangelogBuild build) { - this.build = build; + originBuild = build; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - // todo: get result from here - - if (true) + Task.Run(async () => { - Children = new Drawable[] + var tcs = new TaskCompletionSource(); + + var req = new GetChangelogBuildRequest(originBuild); + req.Success += build => { - new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild }, - new Box + Schedule(() => { - RelativeSizeAxes = Axes.X, - Height = 2, - Colour = colourProvider.Background6, - Margin = new MarginPadding { Top = 30 }, - }, + Children = new Drawable[] + { + new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Colour = colourProvider.Background6, + Margin = new MarginPadding { Top = 30 }, + }, + }; + }); }; - } + req.Failure += _ => + { + // todo: maybe display some + }; + + await req.Perform().ConfigureAwait(false); + + return tcs.Task; + }).Unwrap(); } public partial class ChangelogBuildWithNavigation : ChangelogBuild diff --git a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/Sidebar/ChangelogSidebar.cs b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/Sidebar/ChangelogSidebar.cs index b112f00ea..877b4dafd 100644 --- a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/Sidebar/ChangelogSidebar.cs +++ b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/Sidebar/ChangelogSidebar.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Karaoke.Overlays.Changelog.Sidebar; public partial class ChangelogSidebar : CompositeDrawable { [Cached] - public readonly Bindable Metadata = new(); + public readonly Bindable Metadata = new(); [Cached] public readonly Bindable Year = new(); @@ -109,19 +109,19 @@ protected override void LoadComplete() Year.BindValueChanged(e => onMetadataChanged(Metadata.Value, e.NewValue), true); } - private void onMetadataChanged(APIChangelogSidebar? metadata, int targetYear) + private void onMetadataChanged(APIChangelogIndex? metadata, int targetYear) { changelogsFlow.Clear(); if (metadata == null) return; - var allPosts = metadata.Changelogs; + var builds = metadata.Builds; - if (allPosts.Any() != true) + if (builds.Any() != true) return; - var lookup = metadata.Changelogs.ToLookup(post => post.PublishedAt.Year); + var lookup = builds.ToLookup(post => post.PublishedAt.Year); var posts = lookup[targetYear].ToList(); changelogsFlow.Add(new ChangelogSection(targetYear, posts)); } diff --git a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/Sidebar/YearsPanel.cs b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/Sidebar/YearsPanel.cs index 7246b1901..374626c67 100644 --- a/osu.Game.Rulesets.Karaoke/Overlays/Changelog/Sidebar/YearsPanel.cs +++ b/osu.Game.Rulesets.Karaoke/Overlays/Changelog/Sidebar/YearsPanel.cs @@ -18,12 +18,12 @@ namespace osu.Game.Rulesets.Karaoke.Overlays.Changelog.Sidebar; public partial class YearsPanel : CompositeDrawable { - private readonly Bindable metadata = new(); + private readonly Bindable metadata = new(); private FillFlowContainer yearsFlow = null!; [BackgroundDependencyLoader] - private void load(OverlayColourProvider overlayColours, Bindable metadata) + private void load(OverlayColourProvider overlayColours, Bindable metadata) { this.metadata.BindTo(metadata); diff --git a/osu.Game.Rulesets.Karaoke/Overlays/KaraokeChangelogOverlay.cs b/osu.Game.Rulesets.Karaoke/Overlays/KaraokeChangelogOverlay.cs index 3d0d92dfb..ade40e6cb 100644 --- a/osu.Game.Rulesets.Karaoke/Overlays/KaraokeChangelogOverlay.cs +++ b/osu.Game.Rulesets.Karaoke/Overlays/KaraokeChangelogOverlay.cs @@ -2,23 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Octokit; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Overlays; -using osu.Game.Rulesets.Karaoke.Extensions; +using osu.Game.Rulesets.Karaoke.Online.API.Requests; using osu.Game.Rulesets.Karaoke.Online.API.Requests.Responses; using osu.Game.Rulesets.Karaoke.Overlays.Changelog; using osu.Game.Rulesets.Karaoke.Overlays.Changelog.Sidebar; @@ -39,7 +35,7 @@ public partial class KaraokeChangelogOverlay : OnlineOverlay private Sample? sampleBack; - private readonly List builds = new(); + private APIChangelogIndex? index; private readonly string organizationName; private readonly string branchName; @@ -94,10 +90,16 @@ private void load(AudioManager audio) Current.BindValueChanged(e => { if (e.NewValue != null) + { loadContent(new ChangelogSingleBuild(e.NewValue)); + } + else if (index != null) + { + loadContent(new ChangelogListing(index.PreviewBuilds)); + } else { - loadContent(new ChangelogListing(builds)); + // todo: should show oops content. } }); } @@ -167,13 +169,11 @@ protected override void PopIn() { Current.TriggerChange(); - int[] years = builds.Select(x => x.PublishedAt.Year).Distinct().ToArray(); - sidebar.Year.Value = years.Max(); - sidebar.Metadata.Value = new APIChangelogSidebar - { - Changelogs = builds, - Years = years, - }; + if (index == null) + return; + + sidebar.Year.Value = index.Years.Max(); + sidebar.Metadata.Value = index; }); } } @@ -192,61 +192,24 @@ private Task fetchListing() { var tcs = new TaskCompletionSource(); - var client = new GitHubClient(new ProductHeaderValue(organizationName)); + var req = new GetChangelogRequest(); - try + req.Success += res => Schedule(() => { - var reposAscending = await client.Repository.Content.GetAllContentsByRef(organizationName, projectName, "content/changelog", branchName).ConfigureAwait(false); - - if (reposAscending.Any()) - { - builds.Clear(); - builds.AddRange(reposAscending.Reverse().Where(x => x.Type == ContentType.Dir).Select(x => new APIChangelogBuild(organizationName, projectName, branchName) - { - RootUrl = x.HtmlUrl, - Path = x.Path, - DisplayVersion = x.Name, - PublishedAt = getPublishDateFromName(x.Name), - })); - - foreach (var build in builds) - { - build.Versions.Previous = builds.GetPrevious(build); - build.Versions.Next = builds.GetNext(build); - } + index = res; + tcs.SetResult(true); + }); - tcs.SetResult(true); - } - else - { - tcs.SetResult(false); - } - } - catch (RateLimitExceededException) - { - // todo: maybe show something? - } - catch (Exception) + req.Failure += e => { - // todo: maybe show something? - } - - await tcs.Task.ConfigureAwait(false); - }); + initialFetchTask = null; + tcs.SetException(e); + }; - static DateTimeOffset getPublishDateFromName(string name) - { - var regex = new Regex("(?[-0-9]+).(?[-0-9]{2})(?[-0-9]{2})"); - var result = regex.Match(name); - if (!result.Success) - return DateTimeOffset.MaxValue; - - int year = result.GetGroupValue("year"); - int month = result.GetGroupValue("month"); - int day = result.GetGroupValue("day"); + await req.Perform().ConfigureAwait(false); - return new DateTimeOffset(new DateTime(year, month, day)); - } + return tcs.Task; + }).Unwrap(); } private CancellationTokenSource? loadContentCancellation;