-
-
Notifications
You must be signed in to change notification settings - Fork 317
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2222 from JohnTheGr8/plugin_manifest_enhancements
Rework how we fetch community plugin data
- Loading branch information
Showing
5 changed files
with
134 additions
and
53 deletions.
There are no files selected for viewing
57 changes: 57 additions & 0 deletions
57
Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using Flow.Launcher.Infrastructure.Http; | ||
using Flow.Launcher.Infrastructure.Logger; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Net.Http.Json; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Flow.Launcher.Core.ExternalPlugins | ||
{ | ||
public record CommunityPluginSource(string ManifestFileUrl) | ||
{ | ||
private string latestEtag = ""; | ||
|
||
private List<UserPlugin> plugins = new(); | ||
|
||
/// <summary> | ||
/// Fetch and deserialize the contents of a plugins.json file found at <see cref="ManifestFileUrl"/>. | ||
/// We use conditional http requests to keep repeat requests fast. | ||
/// </summary> | ||
/// <remarks> | ||
/// This method will only return plugin details when the underlying http request is successful (200 or 304). | ||
/// In any other case, an exception is raised | ||
/// </remarks> | ||
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token) | ||
{ | ||
Log.Info(nameof(CommunityPluginSource), $"Loading plugins from {ManifestFileUrl}"); | ||
|
||
var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl); | ||
|
||
request.Headers.Add("If-None-Match", latestEtag); | ||
|
||
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false); | ||
|
||
if (response.StatusCode == HttpStatusCode.OK) | ||
{ | ||
this.plugins = await response.Content.ReadFromJsonAsync<List<UserPlugin>>(cancellationToken: token).ConfigureAwait(false); | ||
this.latestEtag = response.Headers.ETag.Tag; | ||
|
||
Log.Info(nameof(CommunityPluginSource), $"Loaded {this.plugins.Count} plugins from {ManifestFileUrl}"); | ||
return this.plugins; | ||
} | ||
else if (response.StatusCode == HttpStatusCode.NotModified) | ||
{ | ||
Log.Info(nameof(CommunityPluginSource), $"Resource {ManifestFileUrl} has not been modified."); | ||
return this.plugins; | ||
} | ||
else | ||
{ | ||
Log.Warn(nameof(CommunityPluginSource), $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); | ||
throw new Exception($"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); | ||
} | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Flow.Launcher.Core.ExternalPlugins | ||
{ | ||
/// <summary> | ||
/// Describes a store of community-made plugins. | ||
/// The provided URLs should point to a json file, whose content | ||
/// is deserializable as a <see cref="UserPlugin"/> array. | ||
/// </summary> | ||
/// <param name="primaryUrl">Primary URL to the manifest json file.</param> | ||
/// <param name="secondaryUrls">Secondary URLs to access the <paramref name="primaryUrl"/>, for example CDN links</param> | ||
public record CommunityPluginStore(string primaryUrl, params string[] secondaryUrls) | ||
{ | ||
private readonly List<CommunityPluginSource> pluginSources = | ||
secondaryUrls | ||
.Append(primaryUrl) | ||
.Select(url => new CommunityPluginSource(url)) | ||
.ToList(); | ||
|
||
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token, bool onlyFromPrimaryUrl = false) | ||
{ | ||
// we create a new cancellation token source linked to the given token. | ||
// Once any of the http requests completes successfully, we call cancel | ||
// to stop the rest of the running http requests. | ||
var cts = CancellationTokenSource.CreateLinkedTokenSource(token); | ||
|
||
var tasks = onlyFromPrimaryUrl | ||
? new() { pluginSources.Last().FetchAsync(cts.Token) } | ||
: pluginSources.Select(pluginSource => pluginSource.FetchAsync(cts.Token)).ToList(); | ||
|
||
var pluginResults = new List<UserPlugin>(); | ||
|
||
// keep going until all tasks have completed | ||
while (tasks.Any()) | ||
{ | ||
var completedTask = await Task.WhenAny(tasks); | ||
if (completedTask.IsCompletedSuccessfully) | ||
{ | ||
// one of the requests completed successfully; keep its results | ||
// and cancel the remaining http requests. | ||
pluginResults = await completedTask; | ||
cts.Cancel(); | ||
} | ||
tasks.Remove(completedTask); | ||
} | ||
|
||
// all tasks have finished | ||
return pluginResults; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters