Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e8691c2
No need for the wrapped Lazy instance in image loading.
taooceros May 23, 2021
37f1a64
avoid duplicate removing
taooceros May 23, 2021
1c6d207
only modify property when async (avoid duplicate read)
taooceros May 24, 2021
734a0cb
Update comment
taooceros May 24, 2021
e6785dc
standardise flow installer- remove version from exe installer
jjw24 Jul 28, 2021
15d677f
update readme to use standard exe installer
jjw24 Jul 28, 2021
a8d7dcd
remove extra space
jjw24 Jul 28, 2021
b023e8e
update readme
jjw24 Jul 29, 2021
8d04590
fix formatting
jjw24 Jul 29, 2021
8cfd80b
delay the create query helper call
jjw24 Jul 30, 2021
a29d4d1
version bump Explorer
jjw24 Jul 30, 2021
d8eaf88
Merge pull request #620 from Flow-Launcher/fix_queryhelper_call
jjw24 Jul 30, 2021
9e21b2e
include saving SearchPrecisionScore because it is not public before
taooceros Jul 31, 2021
f8675ed
Merge remote-tracking branch 'upstream/dev' into imageOptimize
taooceros Jul 31, 2021
710d2a8
Use ValueTask as return value to suppress potential image error
taooceros Jul 31, 2021
a083528
Use SemaphoreSlim to ensure cache only remove once
taooceros Jul 31, 2021
d2bec9c
Merge pull request #622 from Flow-Launcher/fixSearchPrecisionScoreSave
jjw24 Aug 1, 2021
7d1d41e
version bump
jjw24 Aug 1, 2021
df3b91e
fix PluginsManager's InstallOrUpdate focus
jjw24 Aug 2, 2021
2a06cb9
fix file path tool tip as file path instead
jjw24 Aug 3, 2021
7ada219
Fix tool tip for folder
pc223 Aug 4, 2021
6a1bb17
use < instead of !=
taooceros Aug 5, 2021
7603335
Adjust JsonRPCPlugin.cs Exception Handling
taooceros Aug 6, 2021
1b8c154
Merge pull request #615 from Flow-Launcher/standardise_exe_installer
jjw24 Aug 6, 2021
69cf19e
Merge pull request #630 from Flow-Launcher/optBardWardCompatibility
jjw24 Aug 6, 2021
d524b0a
Merge pull request #628 from Flow-Launcher/fix_pm_update_focus
jjw24 Aug 7, 2021
235fe4a
remove extra comments
jjw24 Aug 7, 2021
7778449
remove extra comments
jjw24 Aug 7, 2021
c344dea
Merge pull request #633 from Flow-Launcher/JsonPRCExceptionHandle
jjw24 Aug 7, 2021
653dc83
fix error thrown in checking cache key when key is null
taooceros Aug 8, 2021
3623e11
add configureAwait(false) in SliceExtra ImageCache
taooceros Aug 8, 2021
d536b6f
change to path instead of subtitle parameter
jjw24 Aug 8, 2021
5aa7412
Merge pull request #450 from Flow-Launcher/imageOptimize
jjw24 Aug 8, 2021
4219dce
Merge pull request #629 from Flow-Launcher/fix_filepath_tooltip
jjw24 Aug 9, 2021
f1ed346
Merge pull request #627 from Flow-Launcher/version_bump_release
jjw24 Aug 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 17 additions & 53 deletions Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,7 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu
public List<Result> LoadContextMenus(Result selectedResult)
{
var output = ExecuteContextMenu(selectedResult);
try
{
return DeserializedResult(output);
}
catch (Exception e)
{
Log.Exception($"|JsonRPCPlugin.LoadContextMenus|Exception on result <{selectedResult}>", e);
return null;
}
return DeserializedResult(output);
}

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

try
{
var queryResponseModel =
await JsonSerializer.DeserializeAsync<JsonRPCQueryResponseModel>(output, options);

return ParseResults(queryResponseModel);
}
catch (JsonException e)
{
Log.Exception(GetType().FullName, "Unexpected Json Input", e);
}
finally
{
await output.DisposeAsync();
}
var queryResponseModel =
await JsonSerializer.DeserializeAsync<JsonRPCQueryResponseModel>(output, options);

return null;
return ParseResults(queryResponseModel);
}

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

var buffer = BufferManager.GetStream();

token.Register(() =>
{
// ReSharper disable once AccessToModifiedClosure
Expand All @@ -274,30 +253,27 @@ protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, Cancellati

token.ThrowIfCancellationRequested();

if (buffer.Length == 0)
{
var errorMessage = process.StandardError.EndOfStream ?
"Empty JSONRPC Response" :
await process.StandardError.ReadToEndAsync();
throw new InvalidDataException($"{context.CurrentPluginMetadata.Name}|{errorMessage}");
}

if (!process.StandardError.EndOfStream)
{
using var standardError = process.StandardError;
var error = await standardError.ReadToEndAsync();

if (!string.IsNullOrEmpty(error))
{
Log.Error($"|JsonRPCPlugin.ExecuteAsync|{error}");
return Stream.Null;
Log.Error($"|{context.CurrentPluginMetadata.Name}.{nameof(ExecuteAsync)}|{error}");
}

Log.Error("|JsonRPCPlugin.ExecuteAsync|Empty standard output and standard error.");
return Stream.Null;
}

return buffer;
}
catch (Exception e)
{
Log.Exception(
$"|JsonRPCPlugin.ExecuteAsync|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>",
e);
return Stream.Null;
}
finally
{
process?.Dispose();
Expand All @@ -307,20 +283,8 @@ protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, Cancellati

public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{
try
{
var output = await ExecuteQueryAsync(query, token);
return await DeserializedResultAsync(output);
}
catch (OperationCanceledException)
{
return null;
}
catch (Exception e)
{
Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e);
return null;
}
var output = await ExecuteQueryAsync(query, token);
return await DeserializedResultAsync(output);
}

public virtual Task InitAsync(PluginInitContext context)
Expand All @@ -329,4 +293,4 @@ public virtual Task InitAsync(PluginInitContext context)
return Task.CompletedTask;
}
}
}
}
27 changes: 19 additions & 8 deletions Flow.Launcher.Infrastructure/Image/ImageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;

Expand All @@ -26,7 +27,8 @@ public class ImageCache
private const int MaxCached = 50;
public ConcurrentDictionary<string, ImageUsage> Data { get; private set; } = new ConcurrentDictionary<string, ImageUsage>();
private const int permissibleFactor = 2;

private SemaphoreSlim semaphore = new(1, 1);

public void Initialization(Dictionary<string, int> usage)
{
foreach (var key in usage.Keys)
Expand Down Expand Up @@ -60,20 +62,29 @@ public ImageSource this[string path]
}
);

// 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
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
if (Data.Count > permissibleFactor * MaxCached)
SliceExtra();

async void SliceExtra()
{
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary.
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key))
Data.TryRemove(key, out _);
// 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
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
if (Data.Count > permissibleFactor * MaxCached)
{
await semaphore.WaitAsync().ConfigureAwait(false);
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary
// Double Check to avoid concurrent remove
if (Data.Count > permissibleFactor * MaxCached)
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key).ToArray())
Data.TryRemove(key, out _);
semaphore.Release();
}
}
}
}

public bool ContainsKey(string key)
{
return Data.ContainsKey(key) && Data[key].imageSource != null;
return key is not null && Data.ContainsKey(key) && Data[key].imageSource != null;
}

public int CacheSize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void UpdatePluginSettings(List<PluginMetadata> metadatas)

// TODO: Remove. This is backwards compatibility for 1.8.0 release.
// Introduced two new action keywords in Explorer, so need to update plugin setting in the UserData folder.
if (metadata.ID == "572be03c74c642baae319fc283e561a8" && metadata.ActionKeywords.Count != settings.ActionKeywords.Count)
if (metadata.ID == "572be03c74c642baae319fc283e561a8" && metadata.ActionKeywords.Count > settings.ActionKeywords.Count)
{
settings.ActionKeywords.Add(Query.GlobalPluginWildcardSign); // for index search
settings.ActionKeywords.Add(Query.GlobalPluginWildcardSign); // for path search
Expand Down
3 changes: 2 additions & 1 deletion Flow.Launcher.Infrastructure/UserSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public string Language
/// </summary>
public bool ShouldUsePinyin { get; set; } = false;

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

[JsonIgnore]
public string QuerySearchPrecisionString
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher/ResultListBox.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<Image x:Name="ImageIcon" Width="32" Height="32" HorizontalAlignment="Left"
Source="{Binding Image.Value}" />
Source="{Binding Image}" />
<Grid Margin="5 0 5 0" Grid.Column="1" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
Expand Down
4 changes: 1 addition & 3 deletions Flow.Launcher/ViewModel/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ async Task updateAction()
}

Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends");
}

;
};

void continueAction(Task t)
{
Expand Down
93 changes: 27 additions & 66 deletions Flow.Launcher/ViewModel/ResultViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,62 +11,12 @@ namespace Flow.Launcher.ViewModel
{
public class ResultViewModel : BaseModel
{
public class LazyAsync<T> : Lazy<ValueTask<T>>
{
private readonly T defaultValue;

private readonly Action _updateCallback;
public new T Value
{
get
{
if (!IsValueCreated)
{
_ = Exercute(); // manually use callback strategy

return defaultValue;
}

if (!base.Value.IsCompletedSuccessfully)
return defaultValue;

return base.Value.Result;

// If none of the variables captured by the local function are captured by other lambdas,
// the compiler can avoid heap allocations.
async ValueTask Exercute()
{
await base.Value.ConfigureAwait(false);
_updateCallback();
}

}
}
public LazyAsync(Func<ValueTask<T>> factory, T defaultValue, Action updateCallback) : base(factory)
{
if (defaultValue != null)
{
this.defaultValue = defaultValue;
}

_updateCallback = updateCallback;
}
}

public ResultViewModel(Result result, Settings settings)
{
if (result != null)
{
Result = result;

Image = new LazyAsync<ImageSource>(
SetImage,
ImageLoader.DefaultImage,
() =>
{
OnPropertyChanged(nameof(Image));
});
}
}

Settings = settings;
}
Expand All @@ -85,44 +35,55 @@ public ResultViewModel(Result result, Settings settings)
? Result.SubTitle
: Result.SubTitleToolTip;

public LazyAsync<ImageSource> Image { get; set; }
private volatile bool ImageLoaded;

private ImageSource image = ImageLoader.DefaultImage;

private async ValueTask<ImageSource> SetImage()
public ImageSource Image
{
get
{
if (!ImageLoaded)
{
ImageLoaded = true;
_ = LoadImageAsync();
}
return image;
}
private set => image = value;
}
private async ValueTask LoadImageAsync()
{
var imagePath = Result.IcoPath;
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
{
try
{
return Result.Icon();
image = Result.Icon();
return;
}
catch (Exception e)
{
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
return ImageLoader.DefaultImage;
}
}

if (ImageLoader.CacheContainImage(imagePath))
{
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
return ImageLoader.Load(imagePath);
image = ImageLoader.Load(imagePath);
return;
}

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

public Result Result { get; }

public override bool Equals(object obj)
{
var r = obj as ResultViewModel;
if (r != null)
{
return Result.Equals(r.Result);
}
else
{
return false;
}
return obj is ResultViewModel r && Result.Equals(r.Result);
}

public override int GetHashCode()
Expand Down
4 changes: 2 additions & 2 deletions Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ internal static Result CreateFolderResult(string title, string subtitle, string
},
Score = score,
TitleToolTip = Constants.ToolTipOpenDirectory,
SubTitleToolTip = Constants.ToolTipOpenDirectory,
SubTitleToolTip = path,
ContextData = new SearchResult
{
Type = ResultType.Folder,
Expand Down Expand Up @@ -144,7 +144,7 @@ internal static Result CreateFileResult(string filePath, Query query, int score
return true;
},
TitleToolTip = Constants.ToolTipOpenContainingFolder,
SubTitleToolTip = Constants.ToolTipOpenContainingFolder,
SubTitleToolTip = filePath,
ContextData = new SearchResult
{
Type = ResultType.File,
Expand Down
6 changes: 3 additions & 3 deletions Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private async Task<List<Result>> WindowsIndexFileContentSearchAsync(Query query,

return await IndexSearch.WindowsIndexSearchAsync(
querySearchString,
queryConstructor.CreateQueryHelper(),
queryConstructor.CreateQueryHelper,
queryConstructor.QueryForFileContentSearch,
Settings.IndexSearchExcludedSubdirectoryPaths,
query,
Expand Down Expand Up @@ -181,7 +181,7 @@ private async Task<List<Result>> WindowsIndexFilesAndFoldersSearchAsync(Query qu

return await IndexSearch.WindowsIndexSearchAsync(
querySearchString,
queryConstructor.CreateQueryHelper(),
queryConstructor.CreateQueryHelper,
queryConstructor.QueryForAllFilesAndFolders,
Settings.IndexSearchExcludedSubdirectoryPaths,
query,
Expand All @@ -195,7 +195,7 @@ private async Task<List<Result>> WindowsIndexTopLevelFolderSearchAsync(Query que

return await IndexSearch.WindowsIndexSearchAsync(
path,
queryConstructor.CreateQueryHelper(),
queryConstructor.CreateQueryHelper,
queryConstructor.QueryForTopLevelDirectorySearch,
Settings.IndexSearchExcludedSubdirectoryPaths,
query,
Expand Down
Loading