Skip to content

Commit 9dacfb1

Browse files
committed
Merge remote-tracking branch 'origin/dev' into jsonrpc_v2
2 parents 24c125f + 1f1940a commit 9dacfb1

File tree

90 files changed

+7362
-274
lines changed

Some content is hidden

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

90 files changed

+7362
-274
lines changed

.github/actions/spelling/expect.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,10 @@ keyevent
9696
KListener
9797
requery
9898
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: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
name: Publish to Winget
22

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

76
jobs:
87
publish:
9-
# Action can only be run on windows
10-
runs-on: windows-latest
8+
runs-on: ubuntu-latest
119
steps:
1210
- uses: vedantmgoyal2009/winget-releaser@v2
1311
with:
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/PluginsManifest.cs

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,38 @@
1-
using Flow.Launcher.Infrastructure.Http;
21
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://jsdelivr.bobocdn.tk/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/Resource/AvailableLanguages.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ internal static class AvailableLanguages
2323
public static Language Spanish_LatinAmerica = new Language("es-419", "Spanish (Latin America)");
2424
public static Language Italian = new Language("it", "Italiano");
2525
public static Language Norwegian_Bokmal = new Language("nb-NO", "Norsk Bokmål");
26-
public static Language Slovak = new Language("sk", "Slovenský");
26+
public static Language Slovak = new Language("sk", "Slovenčina");
2727
public static Language Turkish = new Language("tr", "Türkçe");
28+
public static Language Czech = new Language("cs", "čeština");
29+
public static Language Arabic = new Language("ar", "اللغة العربية");
2830

2931
public static List<Language> GetAvailableLanguages()
3032
{
@@ -50,7 +52,9 @@ public static List<Language> GetAvailableLanguages()
5052
Italian,
5153
Norwegian_Bokmal,
5254
Slovak,
53-
Turkish
55+
Turkish,
56+
Czech,
57+
Arabic
5458
};
5559
return languages;
5660
}

Flow.Launcher.Plugin/EventHandler.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Windows;
1+
using System;
2+
using System.Windows;
23
using System.Windows.Input;
34

45
namespace Flow.Launcher.Plugin
@@ -32,6 +33,24 @@ namespace Flow.Launcher.Plugin
3233
/// <returns>return true to continue handling, return false to intercept system handling</returns>
3334
public delegate bool FlowLauncherGlobalKeyboardEventHandler(int keyevent, int vkcode, SpecialKeyState state);
3435

36+
/// <summary>
37+
/// A delegate for when the visibility is changed
38+
/// </summary>
39+
/// <param name="sender"></param>
40+
/// <param name="args"></param>
41+
public delegate void VisibilityChangedEventHandler(object sender, VisibilityChangedEventArgs args);
42+
43+
/// <summary>
44+
/// The event args for <see cref="VisibilityChangedEventHandler"/>
45+
/// </summary>
46+
public class VisibilityChangedEventArgs : EventArgs
47+
{
48+
/// <summary>
49+
/// <see langword="true"/> if the main window has become visible
50+
/// </summary>
51+
public bool IsVisible { get; init; }
52+
}
53+
3554
/// <summary>
3655
/// Arguments container for the Key Down event
3756
/// </summary>

Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
</PropertyGroup>
1515

1616
<PropertyGroup>
17-
<Version>4.0.1</Version>
18-
<PackageVersion>4.0.1</PackageVersion>
19-
<AssemblyVersion>4.0.1</AssemblyVersion>
20-
<FileVersion>4.0.1</FileVersion>
17+
<Version>4.1.0</Version>
18+
<PackageVersion>4.1.0</PackageVersion>
19+
<AssemblyVersion>4.1.0</AssemblyVersion>
20+
<FileVersion>4.1.0</FileVersion>
2121
<PackageId>Flow.Launcher.Plugin</PackageId>
2222
<Authors>Flow-Launcher</Authors>
2323
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@@ -67,7 +67,7 @@
6767
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
6868
</PackageReference>
6969
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
70-
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
70+
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0" />
7171
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
7272
</ItemGroup>
7373

Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ public interface IPublicAPI
9696
/// </summary>
9797
/// <returns></returns>
9898
bool IsMainWindowVisible();
99-
99+
100+
/// <summary>
101+
/// Invoked when the visibility of the main window has changed. Currently, the plugin will continue to be subscribed even if it is turned off.
102+
/// </summary>
103+
event VisibilityChangedEventHandler VisibilityChanged;
104+
100105
/// <summary>
101106
/// Show message box
102107
/// </summary>

Flow.Launcher.Test/Flow.Launcher.Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
<PrivateAssets>all</PrivateAssets>
5555
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
5656
</PackageReference>
57-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.1" />
57+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
5858
</ItemGroup>
5959

6060
</Project>

0 commit comments

Comments
 (0)