Skip to content

Commit 627a4dc

Browse files
authored
Merge branch 'dev' into dev
2 parents 2ab6055 + fdb1860 commit 627a4dc

File tree

20 files changed

+160
-135
lines changed

20 files changed

+160
-135
lines changed

.github/actions/spelling/expect.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,4 @@ alreadyexists
106106
JsonRPC
107107
JsonRPCV2
108108
Softpedia
109+
img

Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,9 @@ internal sealed class ExecutablePluginV2 : ProcessStreamPluginV2
1212

1313
public ExecutablePluginV2(string filename)
1414
{
15-
StartInfo = new ProcessStartInfo
16-
{
17-
FileName = filename,
18-
UseShellExecute = false,
19-
CreateNoWindow = true,
20-
RedirectStandardOutput = true,
21-
RedirectStandardError = true
22-
};
15+
StartInfo = new ProcessStartInfo { FileName = filename };
2316
}
2417

18+
protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.NewLineDelimited;
2519
}
2620
}

Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ namespace Flow.Launcher.Core.Plugin
1515
{
1616
internal abstract class JsonRPCPluginV2 : JsonRPCPluginBase, IAsyncDisposable, IAsyncReloadable, IResultUpdated
1717
{
18-
public abstract string SupportedLanguage { get; set; }
19-
2018
public const string JsonRpc = "JsonRPC";
2119

2220
protected abstract IDuplexPipe ClientPipe { get; set; }
@@ -41,17 +39,31 @@ protected override async Task<bool> ExecuteResultAsync(JsonRPCResult result)
4139
}
4240
}
4341

42+
private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
43+
4444
public override List<Result> LoadContextMenus(Result selectedResult)
4545
{
46-
throw new NotImplementedException();
46+
try
47+
{
48+
var res = JTF.Run(() => RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("context_menu",
49+
new object[] { selectedResult.ContextData }));
50+
51+
var results = ParseResults(res);
52+
53+
return results;
54+
}
55+
catch
56+
{
57+
return new List<Result>();
58+
}
4759
}
4860

4961
public override async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
5062
{
5163
try
5264
{
5365
var res = await RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("query",
54-
new[] { query },
66+
new object[] { query, Settings.Inner },
5567
token);
5668

5769
var results = ParseResults(res);
@@ -88,12 +100,26 @@ async Task ReadErrorAsync()
88100

89101
public event ResultUpdatedEventHandler ResultsUpdated;
90102

103+
protected enum MessageHandlerType
104+
{
105+
HeaderDelimited,
106+
LengthHeaderDelimited,
107+
NewLineDelimited
108+
}
109+
110+
protected abstract MessageHandlerType MessageHandler { get; }
111+
91112

92113
private void SetupJsonRPC()
93114
{
94115
var formatter = new SystemTextJsonFormatter { JsonSerializerOptions = RequestSerializeOption };
95-
var handler = new NewLineDelimitedMessageHandler(ClientPipe,
96-
formatter);
116+
IJsonRpcMessageHandler handler = MessageHandler switch
117+
{
118+
MessageHandlerType.HeaderDelimited => new HeaderDelimitedMessageHandler(ClientPipe, formatter),
119+
MessageHandlerType.LengthHeaderDelimited => new LengthHeaderMessageHandler(ClientPipe, formatter),
120+
MessageHandlerType.NewLineDelimited => new NewLineDelimitedMessageHandler(ClientPipe, formatter),
121+
_ => throw new ArgumentOutOfRangeException()
122+
};
97123

98124
RPC = new JsonRpc(handler, new JsonRPCPublicAPI(Context.API));
99125

Flow.Launcher.Core/Plugin/NodePluginV2.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,21 @@ namespace Flow.Launcher.Core.Plugin
1010
/// <summary>
1111
/// Execution of JavaScript & TypeScript plugins
1212
/// </summary>
13-
internal class NodePluginV2 : ProcessStreamPluginV2
13+
internal sealed class NodePluginV2 : ProcessStreamPluginV2
1414
{
1515
public NodePluginV2(string filename)
1616
{
17-
StartInfo = new ProcessStartInfo
18-
{
19-
FileName = filename,
20-
UseShellExecute = false,
21-
CreateNoWindow = true,
22-
RedirectStandardOutput = true,
23-
RedirectStandardError = true
24-
};
17+
StartInfo = new ProcessStartInfo { FileName = filename, };
2518
}
2619

27-
public override string SupportedLanguage { get; set; }
2820
protected override ProcessStartInfo StartInfo { get; set; }
2921

3022
public override async Task InitAsync(PluginInitContext context)
3123
{
3224
StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
33-
StartInfo.ArgumentList.Add(string.Empty);
34-
StartInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
3525
await base.InitAsync(context);
3626
}
27+
28+
protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.HeaderDelimited;
3729
}
3830
}

Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
using System;
1+
#nullable enable
2+
3+
using System;
24
using System.Collections.Generic;
35
using System.Diagnostics;
46
using System.IO.Pipelines;
57
using System.Threading.Tasks;
68
using Flow.Launcher.Infrastructure;
79
using Flow.Launcher.Plugin;
810
using Meziantou.Framework.Win32;
11+
using Microsoft.VisualBasic.ApplicationServices;
912
using Nerdbank.Streams;
1013

1114
namespace Flow.Launcher.Core.Plugin
@@ -18,31 +21,37 @@ static ProcessStreamPluginV2()
1821
{
1922
_jobObject.SetLimits(new JobObjectLimits()
2023
{
21-
Flags = JobObjectLimitFlags.KillOnJobClose | JobObjectLimitFlags.DieOnUnhandledException
24+
Flags = JobObjectLimitFlags.KillOnJobClose | JobObjectLimitFlags.DieOnUnhandledException |
25+
JobObjectLimitFlags.SilentBreakawayOk
2226
});
23-
27+
2428
_jobObject.AssignProcess(Process.GetCurrentProcess());
2529
}
2630

27-
public override string SupportedLanguage { get; set; }
28-
protected sealed override IDuplexPipe ClientPipe { get; set; }
31+
protected sealed override IDuplexPipe ClientPipe { get; set; } = null!;
2932

3033
protected abstract ProcessStartInfo StartInfo { get; set; }
3134

32-
protected Process ClientProcess { get; set; }
35+
protected Process ClientProcess { get; set; } = null!;
3336

3437
public override async Task InitAsync(PluginInitContext context)
3538
{
3639
StartInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
3740
StartInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
3841
StartInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
3942

40-
StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
43+
StartInfo.RedirectStandardError = true;
44+
StartInfo.RedirectStandardInput = true;
45+
StartInfo.RedirectStandardOutput = true;
46+
StartInfo.CreateNoWindow = true;
47+
StartInfo.UseShellExecute = false;
4148
StartInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
4249

43-
ClientProcess = Process.Start(StartInfo);
44-
ArgumentNullException.ThrowIfNull(ClientProcess);
45-
50+
var process = Process.Start(StartInfo);
51+
ArgumentNullException.ThrowIfNull(process);
52+
ClientProcess = process;
53+
_jobObject.AssignProcess(ClientProcess);
54+
4655
SetupPipe(ClientProcess);
4756

4857
ErrorStream = ClientProcess.StandardError;

Flow.Launcher.Core/Plugin/PythonPluginV2.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,25 @@ namespace Flow.Launcher.Core.Plugin
1818
{
1919
internal sealed class PythonPluginV2 : ProcessStreamPluginV2
2020
{
21-
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
2221
protected override ProcessStartInfo StartInfo { get; set; }
23-
22+
2423
public PythonPluginV2(string filename)
2524
{
26-
StartInfo = new ProcessStartInfo
27-
{
28-
FileName = filename,
29-
UseShellExecute = false,
30-
CreateNoWindow = true,
31-
RedirectStandardOutput = true,
32-
RedirectStandardError = true,
33-
RedirectStandardInput = true
34-
};
25+
StartInfo = new ProcessStartInfo { FileName = filename, };
3526

3627
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
3728
StartInfo.EnvironmentVariables["PYTHONPATH"] = path;
3829

3930
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
4031
StartInfo.ArgumentList.Add("-B");
4132
}
33+
34+
public override async Task InitAsync(PluginInitContext context)
35+
{
36+
StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
37+
await base.InitAsync(context);
38+
}
39+
40+
protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.NewLineDelimited;
4241
}
4342
}

Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
<ItemGroup>
5151
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
52+
<PackageReference Include="FastCache.Cached" Version="1.8.2" />
5253
<PackageReference Include="Fody" Version="6.5.5">
5354
<PrivateAssets>all</PrivateAssets>
5455
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Flow.Launcher.Infrastructure/Image/ImageCache.cs

Lines changed: 30 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
using System.Linq;
55
using System.Threading;
66
using System.Windows.Media;
7+
using FastCache;
8+
using FastCache.Services;
79

810
namespace Flow.Launcher.Infrastructure.Image
911
{
10-
[Serializable]
1112
public class ImageUsage
1213
{
13-
1414
public int usage;
1515
public ImageSource imageSource;
1616

@@ -23,95 +23,77 @@ public ImageUsage(int usage, ImageSource image)
2323

2424
public class ImageCache
2525
{
26-
private const int MaxCached = 50;
27-
public ConcurrentDictionary<(string, bool), ImageUsage> Data { get; } = new();
28-
private const int permissibleFactor = 2;
29-
private SemaphoreSlim semaphore = new(1, 1);
26+
private const int MaxCached = 150;
3027

3128
public void Initialize(Dictionary<(string, bool), int> usage)
3229
{
3330
foreach (var key in usage.Keys)
3431
{
35-
Data[key] = new ImageUsage(usage[key], null);
32+
Cached<ImageUsage>.Save(key, new ImageUsage(usage[key], null), TimeSpan.MaxValue, MaxCached);
3633
}
3734
}
3835

3936
public ImageSource this[string path, bool isFullImage = false]
4037
{
4138
get
4239
{
43-
if (!Data.TryGetValue((path, isFullImage), out var value))
40+
if (!Cached<ImageUsage>.TryGet((path, isFullImage), out var value))
4441
{
4542
return null;
4643
}
47-
value.usage++;
48-
return value.imageSource;
4944

45+
value.Value.usage++;
46+
return value.Value.imageSource;
5047
}
5148
set
5249
{
53-
Data.AddOrUpdate(
54-
(path, isFullImage),
55-
new ImageUsage(0, value),
56-
(k, v) =>
57-
{
58-
v.imageSource = value;
59-
v.usage++;
60-
return v;
61-
}
62-
);
63-
64-
SliceExtra();
65-
66-
async void SliceExtra()
50+
if (Cached<ImageUsage>.TryGet((path, isFullImage), out var cached))
6751
{
68-
// To prevent the dictionary from drastically increasing in size by caching images, the dictionary size is not allowed to grow more than the permissibleFactor * maxCached size
69-
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
70-
if (Data.Count > permissibleFactor * MaxCached)
71-
{
72-
await semaphore.WaitAsync().ConfigureAwait(false);
73-
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary
74-
// Double Check to avoid concurrent remove
75-
if (Data.Count > permissibleFactor * MaxCached)
76-
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key))
77-
Data.TryRemove(key, out _);
78-
semaphore.Release();
79-
}
52+
cached.Value.imageSource = value;
53+
cached.Value.usage++;
8054
}
55+
56+
Cached<ImageUsage>.Save((path, isFullImage), new ImageUsage(0, value), TimeSpan.MaxValue,
57+
MaxCached);
8158
}
8259
}
8360

8461
public bool ContainsKey(string key, bool isFullImage)
8562
{
86-
return key is not null && Data.ContainsKey((key, isFullImage)) && Data[(key, isFullImage)].imageSource != null;
63+
return Cached<ImageUsage>.TryGet((key, isFullImage), out _);
8764
}
8865

8966
public bool TryGetValue(string key, bool isFullImage, out ImageSource image)
9067
{
91-
if (key is not null)
92-
{
93-
bool hasKey = Data.TryGetValue((key, isFullImage), out var imageUsage);
94-
image = hasKey ? imageUsage.imageSource : null;
95-
return hasKey;
96-
}
97-
else
68+
if (Cached<ImageUsage>.TryGet((key, isFullImage), out var value))
9869
{
99-
image = null;
100-
return false;
70+
image = value.Value.imageSource;
71+
value.Value.usage++;
72+
return image != null;
10173
}
74+
75+
image = null;
76+
return false;
10277
}
10378

10479
public int CacheSize()
10580
{
106-
return Data.Count;
81+
return CacheManager.TotalCount<(string, bool), ImageUsage>();
10782
}
10883

10984
/// <summary>
11085
/// return the number of unique images in the cache (by reference not by checking images content)
11186
/// </summary>
11287
public int UniqueImagesInCache()
11388
{
114-
return Data.Values.Select(x => x.imageSource).Distinct().Count();
89+
return CacheManager.EnumerateEntries<(string, bool), ImageUsage>().Select(x => x.Value.imageSource)
90+
.Distinct()
91+
.Count();
92+
}
93+
94+
public IEnumerable<Cached<(string, bool), ImageUsage>> EnumerateEntries()
95+
{
96+
return CacheManager.EnumerateEntries<(string, bool), ImageUsage>();
11597
}
11698
}
11799
}

0 commit comments

Comments
 (0)