Skip to content

Commit 45e1f39

Browse files
authored
Merge pull request #2197 from Flow-Launcher/dev
Release 1.16.0 | Plugin 4.1.0
2 parents 1c356d0 + 6020cd9 commit 45e1f39

File tree

231 files changed

+8181
-1130
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

231 files changed

+8181
-1130
lines changed

.github/actions/spelling/expect.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ WCA_ACCENT_POLICY
7878
HGlobal
7979
dopusrt
8080
firefox
81+
Firefox
8182
msedge
8283
svgc
8384
ime
@@ -95,3 +96,10 @@ keyevent
9596
KListener
9697
requery
9798
vkcode
99+
čeština
100+
Polski
101+
Srpski
102+
Português
103+
Português (Brasil)
104+
Italiano
105+
Slovenský

.github/workflows/winget.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
name: Publish to Winget
22

33
on:
4-
release:
5-
types: [released]
4+
workflow_dispatch:
65

76
jobs:
87
publish:

Deploy/local_build.ps1

Lines changed: 0 additions & 4 deletions
This file was deleted.

Directory.Build.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Project>
2+
<PropertyGroup>
3+
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
4+
</PropertyGroup>
5+
</Project>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Flow.Launcher.Infrastructure.Http;
2+
using Flow.Launcher.Infrastructure.Logger;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Net.Http.Json;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
namespace Flow.Launcher.Core.ExternalPlugins
12+
{
13+
public record CommunityPluginSource(string ManifestFileUrl)
14+
{
15+
private string latestEtag = "";
16+
17+
private List<UserPlugin> plugins = new();
18+
19+
/// <summary>
20+
/// Fetch and deserialize the contents of a plugins.json file found at <see cref="ManifestFileUrl"/>.
21+
/// We use conditional http requests to keep repeat requests fast.
22+
/// </summary>
23+
/// <remarks>
24+
/// This method will only return plugin details when the underlying http request is successful (200 or 304).
25+
/// In any other case, an exception is raised
26+
/// </remarks>
27+
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token)
28+
{
29+
Log.Info(nameof(CommunityPluginSource), $"Loading plugins from {ManifestFileUrl}");
30+
31+
var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl);
32+
33+
request.Headers.Add("If-None-Match", latestEtag);
34+
35+
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
36+
37+
if (response.StatusCode == HttpStatusCode.OK)
38+
{
39+
this.plugins = await response.Content.ReadFromJsonAsync<List<UserPlugin>>(cancellationToken: token).ConfigureAwait(false);
40+
this.latestEtag = response.Headers.ETag.Tag;
41+
42+
Log.Info(nameof(CommunityPluginSource), $"Loaded {this.plugins.Count} plugins from {ManifestFileUrl}");
43+
return this.plugins;
44+
}
45+
else if (response.StatusCode == HttpStatusCode.NotModified)
46+
{
47+
Log.Info(nameof(CommunityPluginSource), $"Resource {ManifestFileUrl} has not been modified.");
48+
return this.plugins;
49+
}
50+
else
51+
{
52+
Log.Warn(nameof(CommunityPluginSource), $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
53+
throw new Exception($"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
54+
}
55+
}
56+
}
57+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace Flow.Launcher.Core.ExternalPlugins
7+
{
8+
/// <summary>
9+
/// Describes a store of community-made plugins.
10+
/// The provided URLs should point to a json file, whose content
11+
/// is deserializable as a <see cref="UserPlugin"/> array.
12+
/// </summary>
13+
/// <param name="primaryUrl">Primary URL to the manifest json file.</param>
14+
/// <param name="secondaryUrls">Secondary URLs to access the <paramref name="primaryUrl"/>, for example CDN links</param>
15+
public record CommunityPluginStore(string primaryUrl, params string[] secondaryUrls)
16+
{
17+
private readonly List<CommunityPluginSource> pluginSources =
18+
secondaryUrls
19+
.Append(primaryUrl)
20+
.Select(url => new CommunityPluginSource(url))
21+
.ToList();
22+
23+
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token, bool onlyFromPrimaryUrl = false)
24+
{
25+
// we create a new cancellation token source linked to the given token.
26+
// Once any of the http requests completes successfully, we call cancel
27+
// to stop the rest of the running http requests.
28+
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
29+
30+
var tasks = onlyFromPrimaryUrl
31+
? new() { pluginSources.Last().FetchAsync(cts.Token) }
32+
: pluginSources.Select(pluginSource => pluginSource.FetchAsync(cts.Token)).ToList();
33+
34+
var pluginResults = new List<UserPlugin>();
35+
36+
// keep going until all tasks have completed
37+
while (tasks.Any())
38+
{
39+
var completedTask = await Task.WhenAny(tasks);
40+
if (completedTask.IsCompletedSuccessfully)
41+
{
42+
// one of the requests completed successfully; keep its results
43+
// and cancel the remaining http requests.
44+
pluginResults = await completedTask;
45+
cts.Cancel();
46+
}
47+
tasks.Remove(completedTask);
48+
}
49+
50+
// all tasks have finished
51+
return pluginResults;
52+
}
53+
}
54+
}

Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,6 @@ internal IEnumerable<PluginPair> Setup()
4141
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
4242
return new List<PluginPair>();
4343

44-
// TODO: Remove. This is backwards compatibility for 1.10.0 release- changed PythonEmbeded to Environments/Python
45-
if (Language.Equals(AllowedLanguage.Python, StringComparison.OrdinalIgnoreCase))
46-
{
47-
FilesFolders.RemoveFolderIfExists(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable"));
48-
49-
if (!string.IsNullOrEmpty(PluginSettings.PythonDirectory) && PluginSettings.PythonDirectory.StartsWith(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable")))
50-
{
51-
InstallEnvironment();
52-
PluginSettings.PythonDirectory = string.Empty;
53-
}
54-
}
55-
5644
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
5745
{
5846
// Ensure latest only if user is using Flow's environment setup.

Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,38 @@
1-
using Flow.Launcher.Infrastructure.Http;
2-
using Flow.Launcher.Infrastructure.Logger;
1+
using Flow.Launcher.Infrastructure.Logger;
32
using System;
43
using System.Collections.Generic;
5-
using System.Net;
6-
using System.Net.Http;
7-
using System.Text.Json;
84
using System.Threading;
95
using System.Threading.Tasks;
106

117
namespace Flow.Launcher.Core.ExternalPlugins
128
{
139
public static class PluginsManifest
1410
{
15-
private const string manifestFileUrl = "https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json";
11+
private static readonly CommunityPluginStore mainPluginStore =
12+
new("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/plugin_api_v2/plugins.json",
13+
"https://fastly.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json",
14+
"https://gcore.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json",
15+
"https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json");
1616

1717
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
1818

19-
private static string latestEtag = "";
19+
private static DateTime lastFetchedAt = DateTime.MinValue;
20+
private static TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
2021

21-
public static List<UserPlugin> UserPlugins { get; private set; } = new List<UserPlugin>();
22+
public static List<UserPlugin> UserPlugins { get; private set; }
2223

23-
public static async Task UpdateManifestAsync(CancellationToken token = default)
24+
public static async Task UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false)
2425
{
2526
try
2627
{
2728
await manifestUpdateLock.WaitAsync(token).ConfigureAwait(false);
2829

29-
var request = new HttpRequestMessage(HttpMethod.Get, manifestFileUrl);
30-
request.Headers.Add("If-None-Match", latestEtag);
31-
32-
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
33-
34-
if (response.StatusCode == HttpStatusCode.OK)
30+
if (UserPlugins == null || usePrimaryUrlOnly || DateTime.Now.Subtract(lastFetchedAt) >= fetchTimeout)
3531
{
36-
Log.Info($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Fetched plugins from manifest repo");
37-
38-
await using var json = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false);
32+
var results = await mainPluginStore.FetchAsync(token, usePrimaryUrlOnly).ConfigureAwait(false);
3933

40-
UserPlugins = await JsonSerializer.DeserializeAsync<List<UserPlugin>>(json, cancellationToken: token).ConfigureAwait(false);
41-
42-
latestEtag = response.Headers.ETag.Tag;
43-
}
44-
else if (response.StatusCode != HttpStatusCode.NotModified)
45-
{
46-
Log.Warn($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http response for manifest file was {response.StatusCode}");
34+
UserPlugins = results;
35+
lastFetchedAt = DateTime.Now;
4736
}
4837
}
4938
catch (Exception e)

Flow.Launcher.Core/Flow.Launcher.Core.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>net7.0-windows</TargetFramework>
@@ -54,8 +54,8 @@
5454

5555
<ItemGroup>
5656
<PackageReference Include="Droplex" Version="1.6.0" />
57-
<PackageReference Include="FSharp.Core" Version="7.0.0" />
58-
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.1" />
57+
<PackageReference Include="FSharp.Core" Version="7.0.300" />
58+
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
5959
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
6060
</ItemGroup>
6161

Flow.Launcher.Core/Plugin/ExecutablePlugin.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.IO;
33
using System.Threading;
44
using System.Threading.Tasks;
5-
using Flow.Launcher.Plugin;
65

76
namespace Flow.Launcher.Core.Plugin
87
{

0 commit comments

Comments
 (0)