diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 8a95ee79f77..2889e5ec7ed 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -31,6 +31,7 @@ public static class Constant public static readonly string ErrorIcon = Path.Combine(ImagesDirectory, "app_error.png"); public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png"); public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png"); + public static readonly string ImageIcon = Path.Combine(ImagesDirectory, "image.png"); public static string PythonPath; public static string NodePath; diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 612f495be64..018c4f7c9e6 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -22,6 +22,7 @@ public static class ImageLoader private static readonly ConcurrentDictionary GuidToKey = new(); private static IImageHashGenerator _hashGenerator; private static readonly bool EnableImageHash = true; + public static ImageSource Image { get; } = new BitmapImage(new Uri(Constant.ImageIcon)); public static ImageSource MissingImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); public static ImageSource LoadingImage { get; } = new BitmapImage(new Uri(Constant.LoadingImgIcon)); public const int SmallIconSize = 64; @@ -215,8 +216,16 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag type = ImageType.ImageFile; if (loadFullImage) { - image = LoadFullImage(path); - type = ImageType.FullImageFile; + try + { + image = LoadFullImage(path); + type = ImageType.FullImageFile; + } + catch (NotSupportedException) + { + image = Image; + type = ImageType.Error; + } } else { diff --git a/Flow.Launcher/Images/image.png b/Flow.Launcher/Images/image.png index 9f26517e84e..ea610046b22 100644 Binary files a/Flow.Launcher/Images/image.png and b/Flow.Launcher/Images/image.png differ diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index b9ad975345c..f5fd729d45b 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -308,7 +308,7 @@ VerticalAlignment="Center" Panel.ZIndex="2" RenderOptions.BitmapScalingMode="HighQuality" - Source="{Binding PluginIconPath}" + Source="{Binding PluginIconSource}" Stretch="Uniform" Style="{DynamicResource PluginActivationIcon}" /> diff --git a/Flow.Launcher/Storage/TopMostRecord.cs b/Flow.Launcher/Storage/TopMostRecord.cs index 052c296cdde..cbd0b88fc7e 100644 --- a/Flow.Launcher/Storage/TopMostRecord.cs +++ b/Flow.Launcher/Storage/TopMostRecord.cs @@ -1,29 +1,30 @@ -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Text.Json.Serialization; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage { - // todo this class is not thread safe.... but used from multiple threads. public class TopMostRecord { [JsonInclude] - public Dictionary records { get; private set; } = new Dictionary(); + public ConcurrentDictionary records { get; private set; } = new ConcurrentDictionary(); internal bool IsTopMost(Result result) { - if (records.Count == 0 || !records.ContainsKey(result.OriginQuery.RawQuery)) + if (records.IsEmpty || result.OriginQuery == null || + !records.TryGetValue(result.OriginQuery.RawQuery, out var value)) { return false; } // since this dictionary should be very small (or empty) going over it should be pretty fast. - return records[result.OriginQuery.RawQuery].Equals(result); + return value.Equals(result); } internal void Remove(Result result) { - records.Remove(result.OriginQuery.RawQuery); + records.Remove(result.OriginQuery.RawQuery, out _); } internal void AddOrUpdate(Result result) @@ -34,17 +35,15 @@ internal void AddOrUpdate(Result result) Title = result.Title, SubTitle = result.SubTitle }; - records[result.OriginQuery.RawQuery] = record; - + records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record); } public void Load(Dictionary dictionary) { - records = dictionary; + records = new ConcurrentDictionary(dictionary); } } - public class Record { public string Title { get; set; } diff --git a/Flow.Launcher/Storage/UserSelectedRecord.cs b/Flow.Launcher/Storage/UserSelectedRecord.cs index 6afe1e91f8d..d6405005dba 100644 --- a/Flow.Launcher/Storage/UserSelectedRecord.cs +++ b/Flow.Launcher/Storage/UserSelectedRecord.cs @@ -51,6 +51,11 @@ private static int GenerateResultHashCode(Result result) private static int GenerateQueryAndResultHashCode(Query query, Result result) { + if (query == null) + { + return GenerateResultHashCode(result); + } + int hashcode = GenerateStaticHashCode(query.ActionKeyword); hashcode = GenerateStaticHashCode(query.Search, hashcode); hashcode = GenerateStaticHashCode(result.Title, hashcode); @@ -101,4 +106,4 @@ public int GetSelectedCount(Result result) return selectedCount; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 6c17e21f0d2..f964be7954d 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -23,6 +23,8 @@ using System.Globalization; using System.Windows.Input; using System.ComponentModel; +using Flow.Launcher.Infrastructure.Image; +using System.Windows.Media; namespace Flow.Launcher.ViewModel { @@ -722,6 +724,8 @@ public double ResultSubItemFontSize set => Settings.ResultSubItemFontSize = value; } + public ImageSource PluginIconSource { get; private set; } = null; + public string PluginIconPath { get; set; } = null; public string OpenResultCommandModifiers => Settings.OpenResultModifiers; @@ -1066,6 +1070,7 @@ private async void QueryResults(bool isReQuery = false, bool reSelect = true) Results.Clear(); Results.Visibility = Visibility.Collapsed; PluginIconPath = null; + PluginIconSource = null; SearchIconVisibility = Visibility.Visible; return; } @@ -1099,11 +1104,13 @@ private async void QueryResults(bool isReQuery = false, bool reSelect = true) if (plugins.Count == 1) { PluginIconPath = plugins.Single().Metadata.IcoPath; + PluginIconSource = await ImageLoader.LoadAsync(PluginIconPath); SearchIconVisibility = Visibility.Hidden; } else { PluginIconPath = null; + PluginIconSource = null; SearchIconVisibility = Visibility.Visible; }