Skip to content

Commit 151c69e

Browse files
authored
Merge pull request #626 from Flow-Launcher/dev
Release 1.8.2
2 parents 620ed4a + f1ed346 commit 151c69e

File tree

16 files changed

+88
-151
lines changed

16 files changed

+88
-151
lines changed

Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,7 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu
4040
public List<Result> LoadContextMenus(Result selectedResult)
4141
{
4242
var output = ExecuteContextMenu(selectedResult);
43-
try
44-
{
45-
return DeserializedResult(output);
46-
}
47-
catch (Exception e)
48-
{
49-
Log.Exception($"|JsonRPCPlugin.LoadContextMenus|Exception on result <{selectedResult}>", e);
50-
return null;
51-
}
43+
return DeserializedResult(output);
5244
}
5345

5446
private static readonly JsonSerializerOptions options = new()
@@ -65,23 +57,10 @@ private async Task<List<Result>> DeserializedResultAsync(Stream output)
6557
{
6658
if (output == Stream.Null) return null;
6759

68-
try
69-
{
70-
var queryResponseModel =
71-
await JsonSerializer.DeserializeAsync<JsonRPCQueryResponseModel>(output, options);
72-
73-
return ParseResults(queryResponseModel);
74-
}
75-
catch (JsonException e)
76-
{
77-
Log.Exception(GetType().FullName, "Unexpected Json Input", e);
78-
}
79-
finally
80-
{
81-
await output.DisposeAsync();
82-
}
60+
var queryResponseModel =
61+
await JsonSerializer.DeserializeAsync<JsonRPCQueryResponseModel>(output, options);
8362

84-
return null;
63+
return ParseResults(queryResponseModel);
8564
}
8665

8766
private List<Result> DeserializedResult(string output)
@@ -249,7 +228,7 @@ protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, Cancellati
249228
await using var source = process.StandardOutput.BaseStream;
250229

251230
var buffer = BufferManager.GetStream();
252-
231+
253232
token.Register(() =>
254233
{
255234
// ReSharper disable once AccessToModifiedClosure
@@ -274,30 +253,27 @@ protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, Cancellati
274253

275254
token.ThrowIfCancellationRequested();
276255

256+
if (buffer.Length == 0)
257+
{
258+
var errorMessage = process.StandardError.EndOfStream ?
259+
"Empty JSONRPC Response" :
260+
await process.StandardError.ReadToEndAsync();
261+
throw new InvalidDataException($"{context.CurrentPluginMetadata.Name}|{errorMessage}");
262+
}
263+
277264
if (!process.StandardError.EndOfStream)
278265
{
279266
using var standardError = process.StandardError;
280267
var error = await standardError.ReadToEndAsync();
281268

282269
if (!string.IsNullOrEmpty(error))
283270
{
284-
Log.Error($"|JsonRPCPlugin.ExecuteAsync|{error}");
285-
return Stream.Null;
271+
Log.Error($"|{context.CurrentPluginMetadata.Name}.{nameof(ExecuteAsync)}|{error}");
286272
}
287-
288-
Log.Error("|JsonRPCPlugin.ExecuteAsync|Empty standard output and standard error.");
289-
return Stream.Null;
290273
}
291274

292275
return buffer;
293276
}
294-
catch (Exception e)
295-
{
296-
Log.Exception(
297-
$"|JsonRPCPlugin.ExecuteAsync|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>",
298-
e);
299-
return Stream.Null;
300-
}
301277
finally
302278
{
303279
process?.Dispose();
@@ -307,20 +283,8 @@ protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, Cancellati
307283

308284
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
309285
{
310-
try
311-
{
312-
var output = await ExecuteQueryAsync(query, token);
313-
return await DeserializedResultAsync(output);
314-
}
315-
catch (OperationCanceledException)
316-
{
317-
return null;
318-
}
319-
catch (Exception e)
320-
{
321-
Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e);
322-
return null;
323-
}
286+
var output = await ExecuteQueryAsync(query, token);
287+
return await DeserializedResultAsync(output);
324288
}
325289

326290
public virtual Task InitAsync(PluginInitContext context)
@@ -329,4 +293,4 @@ public virtual Task InitAsync(PluginInitContext context)
329293
return Task.CompletedTask;
330294
}
331295
}
332-
}
296+
}

Flow.Launcher.Infrastructure/Image/ImageCache.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using System.Threading;
56
using System.Threading.Tasks;
67
using System.Windows.Media;
78

@@ -26,7 +27,8 @@ public class ImageCache
2627
private const int MaxCached = 50;
2728
public ConcurrentDictionary<string, ImageUsage> Data { get; private set; } = new ConcurrentDictionary<string, ImageUsage>();
2829
private const int permissibleFactor = 2;
29-
30+
private SemaphoreSlim semaphore = new(1, 1);
31+
3032
public void Initialization(Dictionary<string, int> usage)
3133
{
3234
foreach (var key in usage.Keys)
@@ -60,20 +62,29 @@ public ImageSource this[string path]
6062
}
6163
);
6264

63-
// 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
64-
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
65-
if (Data.Count > permissibleFactor * MaxCached)
65+
SliceExtra();
66+
67+
async void SliceExtra()
6668
{
67-
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary.
68-
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key))
69-
Data.TryRemove(key, out _);
69+
// 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
70+
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
71+
if (Data.Count > permissibleFactor * MaxCached)
72+
{
73+
await semaphore.WaitAsync().ConfigureAwait(false);
74+
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary
75+
// Double Check to avoid concurrent remove
76+
if (Data.Count > permissibleFactor * MaxCached)
77+
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key).ToArray())
78+
Data.TryRemove(key, out _);
79+
semaphore.Release();
80+
}
7081
}
7182
}
7283
}
7384

7485
public bool ContainsKey(string key)
7586
{
76-
return Data.ContainsKey(key) && Data[key].imageSource != null;
87+
return key is not null && Data.ContainsKey(key) && Data[key].imageSource != null;
7788
}
7889

7990
public int CacheSize()

Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void UpdatePluginSettings(List<PluginMetadata> metadatas)
1818

1919
// TODO: Remove. This is backwards compatibility for 1.8.0 release.
2020
// Introduced two new action keywords in Explorer, so need to update plugin setting in the UserData folder.
21-
if (metadata.ID == "572be03c74c642baae319fc283e561a8" && metadata.ActionKeywords.Count != settings.ActionKeywords.Count)
21+
if (metadata.ID == "572be03c74c642baae319fc283e561a8" && metadata.ActionKeywords.Count > settings.ActionKeywords.Count)
2222
{
2323
settings.ActionKeywords.Add(Query.GlobalPluginWildcardSign); // for index search
2424
settings.ActionKeywords.Add(Query.GlobalPluginWildcardSign); // for path search

Flow.Launcher.Infrastructure/UserSettings/Settings.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public string Language
3939
/// </summary>
4040
public bool ShouldUsePinyin { get; set; } = false;
4141

42-
internal SearchPrecisionScore QuerySearchPrecision { get; private set; } = SearchPrecisionScore.Regular;
42+
[JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))]
43+
public SearchPrecisionScore QuerySearchPrecision { get; private set; } = SearchPrecisionScore.Regular;
4344

4445
[JsonIgnore]
4546
public string QuerySearchPrecisionString

Flow.Launcher/ResultListBox.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<ColumnDefinition Width="0" />
4343
</Grid.ColumnDefinitions>
4444
<Image x:Name="ImageIcon" Width="32" Height="32" HorizontalAlignment="Left"
45-
Source="{Binding Image.Value}" />
45+
Source="{Binding Image}" />
4646
<Grid Margin="5 0 5 0" Grid.Column="1" HorizontalAlignment="Stretch">
4747
<Grid.RowDefinitions>
4848
<RowDefinition />

Flow.Launcher/ViewModel/MainViewModel.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,7 @@ async Task updateAction()
108108
}
109109

110110
Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends");
111-
}
112-
113-
;
111+
};
114112

115113
void continueAction(Task t)
116114
{

Flow.Launcher/ViewModel/ResultViewModel.cs

Lines changed: 27 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -11,62 +11,12 @@ namespace Flow.Launcher.ViewModel
1111
{
1212
public class ResultViewModel : BaseModel
1313
{
14-
public class LazyAsync<T> : Lazy<ValueTask<T>>
15-
{
16-
private readonly T defaultValue;
17-
18-
private readonly Action _updateCallback;
19-
public new T Value
20-
{
21-
get
22-
{
23-
if (!IsValueCreated)
24-
{
25-
_ = Exercute(); // manually use callback strategy
26-
27-
return defaultValue;
28-
}
29-
30-
if (!base.Value.IsCompletedSuccessfully)
31-
return defaultValue;
32-
33-
return base.Value.Result;
34-
35-
// If none of the variables captured by the local function are captured by other lambdas,
36-
// the compiler can avoid heap allocations.
37-
async ValueTask Exercute()
38-
{
39-
await base.Value.ConfigureAwait(false);
40-
_updateCallback();
41-
}
42-
43-
}
44-
}
45-
public LazyAsync(Func<ValueTask<T>> factory, T defaultValue, Action updateCallback) : base(factory)
46-
{
47-
if (defaultValue != null)
48-
{
49-
this.defaultValue = defaultValue;
50-
}
51-
52-
_updateCallback = updateCallback;
53-
}
54-
}
55-
5614
public ResultViewModel(Result result, Settings settings)
5715
{
5816
if (result != null)
5917
{
6018
Result = result;
61-
62-
Image = new LazyAsync<ImageSource>(
63-
SetImage,
64-
ImageLoader.DefaultImage,
65-
() =>
66-
{
67-
OnPropertyChanged(nameof(Image));
68-
});
69-
}
19+
}
7020

7121
Settings = settings;
7222
}
@@ -85,44 +35,55 @@ public ResultViewModel(Result result, Settings settings)
8535
? Result.SubTitle
8636
: Result.SubTitleToolTip;
8737

88-
public LazyAsync<ImageSource> Image { get; set; }
38+
private volatile bool ImageLoaded;
39+
40+
private ImageSource image = ImageLoader.DefaultImage;
8941

90-
private async ValueTask<ImageSource> SetImage()
42+
public ImageSource Image
43+
{
44+
get
45+
{
46+
if (!ImageLoaded)
47+
{
48+
ImageLoaded = true;
49+
_ = LoadImageAsync();
50+
}
51+
return image;
52+
}
53+
private set => image = value;
54+
}
55+
private async ValueTask LoadImageAsync()
9156
{
9257
var imagePath = Result.IcoPath;
9358
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
9459
{
9560
try
9661
{
97-
return Result.Icon();
62+
image = Result.Icon();
63+
return;
9864
}
9965
catch (Exception e)
10066
{
10167
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
102-
return ImageLoader.DefaultImage;
10368
}
10469
}
10570

10671
if (ImageLoader.CacheContainImage(imagePath))
72+
{
10773
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
108-
return ImageLoader.Load(imagePath);
74+
image = ImageLoader.Load(imagePath);
75+
return;
76+
}
10977

110-
return await Task.Run(() => ImageLoader.Load(imagePath));
78+
// We need to modify the property not field here to trigger the OnPropertyChanged event
79+
Image = await Task.Run(() => ImageLoader.Load(imagePath)).ConfigureAwait(false);
11180
}
11281

11382
public Result Result { get; }
11483

11584
public override bool Equals(object obj)
11685
{
117-
var r = obj as ResultViewModel;
118-
if (r != null)
119-
{
120-
return Result.Equals(r.Result);
121-
}
122-
else
123-
{
124-
return false;
125-
}
86+
return obj is ResultViewModel r && Result.Equals(r.Result);
12687
}
12788

12889
public override int GetHashCode()

Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ internal static Result CreateFolderResult(string title, string subtitle, string
5252
},
5353
Score = score,
5454
TitleToolTip = Constants.ToolTipOpenDirectory,
55-
SubTitleToolTip = Constants.ToolTipOpenDirectory,
55+
SubTitleToolTip = path,
5656
ContextData = new SearchResult
5757
{
5858
Type = ResultType.Folder,
@@ -144,7 +144,7 @@ internal static Result CreateFileResult(string filePath, Query query, int score
144144
return true;
145145
},
146146
TitleToolTip = Constants.ToolTipOpenContainingFolder,
147-
SubTitleToolTip = Constants.ToolTipOpenContainingFolder,
147+
SubTitleToolTip = filePath,
148148
ContextData = new SearchResult
149149
{
150150
Type = ResultType.File,

Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private async Task<List<Result>> WindowsIndexFileContentSearchAsync(Query query,
143143

144144
return await IndexSearch.WindowsIndexSearchAsync(
145145
querySearchString,
146-
queryConstructor.CreateQueryHelper(),
146+
queryConstructor.CreateQueryHelper,
147147
queryConstructor.QueryForFileContentSearch,
148148
Settings.IndexSearchExcludedSubdirectoryPaths,
149149
query,
@@ -181,7 +181,7 @@ private async Task<List<Result>> WindowsIndexFilesAndFoldersSearchAsync(Query qu
181181

182182
return await IndexSearch.WindowsIndexSearchAsync(
183183
querySearchString,
184-
queryConstructor.CreateQueryHelper(),
184+
queryConstructor.CreateQueryHelper,
185185
queryConstructor.QueryForAllFilesAndFolders,
186186
Settings.IndexSearchExcludedSubdirectoryPaths,
187187
query,
@@ -195,7 +195,7 @@ private async Task<List<Result>> WindowsIndexTopLevelFolderSearchAsync(Query que
195195

196196
return await IndexSearch.WindowsIndexSearchAsync(
197197
path,
198-
queryConstructor.CreateQueryHelper(),
198+
queryConstructor.CreateQueryHelper,
199199
queryConstructor.QueryForTopLevelDirectorySearch,
200200
Settings.IndexSearchExcludedSubdirectoryPaths,
201201
query,

0 commit comments

Comments
 (0)