diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 80c6684f5..0cb5779d2 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -26,7 +26,7 @@ namespace Flow.Launcher.Infrastructure.Image private const int MaxCached = 50; public ConcurrentDictionary Data { get; private set; } = new ConcurrentDictionary(); private const int permissibleFactor = 2; - + public void Initialization(Dictionary usage) { foreach (var key in usage.Keys) @@ -44,14 +44,14 @@ namespace Flow.Launcher.Infrastructure.Image value.usage++; return value.imageSource; } - + return null; } set { Data.AddOrUpdate( - path, - new ImageUsage(0, value), + path, + new ImageUsage(0, value), (k, v) => { v.imageSource = value; @@ -66,7 +66,6 @@ namespace Flow.Launcher.Infrastructure.Image { // 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)) { if (!(key.Equals(Constant.ErrorIcon) || key.Equals(Constant.DefaultIcon))) @@ -80,7 +79,7 @@ namespace Flow.Launcher.Infrastructure.Image public bool ContainsKey(string key) { - var contains = Data.ContainsKey(key); + var contains = Data.ContainsKey(key) && Data[key] != null; return contains; } @@ -97,5 +96,4 @@ namespace Flow.Launcher.Infrastructure.Image return Data.Values.Select(x => x.imageSource).Distinct().Count(); } } - } \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index edfb88cbf..ac333d567 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -18,6 +18,8 @@ namespace Flow.Launcher.Infrastructure.Image private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); private static IImageHashGenerator _hashGenerator; private static bool EnableImageHash = true; + public static ImageSource DefaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); + private static readonly string[] ImageExtensions = { @@ -61,7 +63,7 @@ namespace Flow.Launcher.Infrastructure.Image { lock (_storage) { - _storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, y => y.usage)); + _storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, x => x.usage)); } } @@ -211,6 +213,11 @@ namespace Flow.Launcher.Infrastructure.Image option); } + public static bool CacheContainImage(string path) + { + return ImageCache.ContainsKey(path) && ImageCache[path] != null; + } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); @@ -221,7 +228,7 @@ namespace Flow.Launcher.Infrastructure.Image string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; if (hash != null) { - + if (GuidToKey.TryGetValue(hash, out string key)) { // image already exists img = ImageCache[key] ?? img; diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 3280dc457..072196605 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -42,7 +42,7 @@ + Source="{Binding Image.Value}" /> diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index a4fe2ede4..00a0e1ae5 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,23 +1,66 @@ using System; +using System.Threading.Tasks; using System.Windows; using System.Windows.Media; -using System.Windows.Threading; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; - namespace Flow.Launcher.ViewModel { public class ResultViewModel : BaseModel { + public class LazyAsync : Lazy> + { + private T defaultValue; + + private readonly Action _updateCallback; + public new T Value + { + get + { + if (!IsValueCreated) + { + base.Value.ContinueWith(_ => + { + _updateCallback(); + }); + + return defaultValue; + } + + if (!base.Value.IsCompleted || base.Value.IsFaulted) + return defaultValue; + + return base.Value.Result; + } + } + public LazyAsync(Func> 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( + SetImage, + ImageLoader.DefaultImage, + () => + { + OnPropertyChanged(nameof(Image)); + }); } Settings = settings; @@ -25,39 +68,45 @@ namespace Flow.Launcher.ViewModel public Settings Settings { get; private set; } - public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Hidden; + public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Hidden; public string OpenResultModifiers => Settings.OpenResultModifiers; public string ShowTitleToolTip => string.IsNullOrEmpty(Result.TitleToolTip) - ? Result.Title + ? Result.Title : Result.TitleToolTip; public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip) - ? Result.SubTitle + ? Result.SubTitle : Result.SubTitleToolTip; - public ImageSource Image + public LazyAsync Image { get; set; } + + private async Task SetImage() { - get + var imagePath = Result.IcoPath; + if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) { - var imagePath = Result.IcoPath; - if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) + try { - try - { - return Result.Icon(); - } - catch (Exception e) - { - Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); - imagePath = Constant.MissingImgIcon; - } + return Result.Icon(); } - + catch (Exception e) + { + Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); + imagePath = Constant.MissingImgIcon; + } + } + + 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); } + else + { + return await Task.Run(() => ImageLoader.Load(imagePath)); + } } public Result Result { get; } @@ -84,6 +133,5 @@ namespace Flow.Launcher.ViewModel { return Result.ToString(); } - } }