From 2c14c2120f83eb4761efc91dd5190daac0f60e51 Mon Sep 17 00:00:00 2001 From: Qian Bao Date: Fri, 23 Oct 2020 10:45:22 +0800 Subject: [PATCH 1/8] Lazy Load Image --- Flow.Launcher/ResultListBox.xaml | 2 +- Flow.Launcher/ViewModel/ResultViewModel.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 3280dc457a1..072196605a6 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 a4fe2ede4fc..a64836285e4 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -18,6 +18,7 @@ public ResultViewModel(Result result, Settings settings) if (result != null) { Result = result; + Image = new Lazy(() => SetImage); } Settings = settings; @@ -36,8 +37,10 @@ public ResultViewModel(Result result, Settings settings) public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip) ? Result.SubTitle : Result.SubTitleToolTip; + + public Lazy Image { get; set; } - public ImageSource Image + private ImageSource SetImage { get { @@ -75,6 +78,7 @@ public override bool Equals(object obj) } } + public override int GetHashCode() { return Result.GetHashCode(); From 1439ee7e9ed6adbcc5f17221dca1eb406c00ae58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 20:45:00 +0800 Subject: [PATCH 2/8] Use a default image to list when the image hasn't been loaded in cache. Co-authored-by: Bao-Qian --- .../Image/ImageCache.cs | 13 +++++---- .../Image/ImageLoader.cs | 11 +++++++- Flow.Launcher/ViewModel/ResultViewModel.cs | 28 +++++++++++++------ 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 80c6684f55c..7482ac1cfec 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -26,7 +26,7 @@ public class ImageCache 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 @@ public ImageSource this[string path] 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; @@ -67,7 +67,8 @@ public ImageSource this[string path] // 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)) + foreach (var key in Data.Where(x => x.Key != Constant.MissingImgIcon) + .OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) { if (!(key.Equals(Constant.ErrorIcon) || key.Equals(Constant.DefaultIcon))) { @@ -80,7 +81,7 @@ public ImageSource this[string path] public bool ContainsKey(string key) { - var contains = Data.ContainsKey(key); + var contains = Data.ContainsKey(key) && Data[key] != null; return contains; } diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index edfb88cbfe6..bc924926c1c 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -61,7 +61,7 @@ public static void Save() { 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 +211,15 @@ private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = option); } + public static bool CacheContainImage(string path) + { + return ImageCache.ContainsKey(path); + } + public static ImageSource LoadDefault(bool loadFullImage = false) + { + return LoadInternal(Constant.MissingImgIcon, loadFullImage).ImageSource; + } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index a64836285e4..76c2a3e48d0 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Threading; @@ -26,18 +27,18 @@ public ResultViewModel(Result result, Settings settings) 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 Lazy Image { get; set; } private ImageSource SetImage @@ -57,9 +58,20 @@ private ImageSource SetImage imagePath = Constant.MissingImgIcon; } } - - // will get here either when icoPath has value\icon delegate is null\when had exception in delegate - return ImageLoader.Load(imagePath); + + 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 + { + Task.Run(() => + { + Image = new Lazy(() => ImageLoader.Load(imagePath)); + OnPropertyChanged(nameof(Image)); + }); + + return ImageLoader.LoadDefault(); + } } } @@ -78,7 +90,7 @@ public override bool Equals(object obj) } } - + public override int GetHashCode() { return Result.GetHashCode(); From 99086e26444498bc5ebe0a7ca5654eeedb99dc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 10:12:32 +0800 Subject: [PATCH 3/8] change SetImage to method instead of property to avoid unintended use --- Flow.Launcher/ViewModel/ResultViewModel.cs | 47 ++++++++++------------ 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 76c2a3e48d0..8ce35227f08 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -19,7 +19,7 @@ public ResultViewModel(Result result, Settings settings) if (result != null) { Result = result; - Image = new Lazy(() => SetImage); + Image = new Lazy(SetImage); } Settings = settings; @@ -41,37 +41,34 @@ public ResultViewModel(Result result, Settings settings) public Lazy Image { get; set; } - private ImageSource SetImage + private ImageSource 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 + 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 + { + Task.Run(() => { - Task.Run(() => - { - Image = new Lazy(() => ImageLoader.Load(imagePath)); - OnPropertyChanged(nameof(Image)); - }); + Image = new Lazy(() => ImageLoader.Load(imagePath)); + OnPropertyChanged(nameof(Image)); + }); - return ImageLoader.LoadDefault(); - } + return ImageLoader.LoadDefault(); } } From a7310d5d22663af9119bc0d96b7fd0fa849afbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 12:46:39 +0800 Subject: [PATCH 4/8] Use Customized LazyAsync class to load image instead of the weird way of updating lazy class async --- .../Image/ImageLoader.cs | 12 ++-- Flow.Launcher/ViewModel/ResultViewModel.cs | 56 +++++++++++++++---- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index bc924926c1c..f61d4615a47 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -18,6 +18,8 @@ public static class ImageLoader 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 = { @@ -37,6 +39,7 @@ public static void Initialize() var usage = LoadStorageToConcurrentDictionary(); + foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); @@ -213,13 +216,10 @@ private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = public static bool CacheContainImage(string path) { - return ImageCache.ContainsKey(path); - } - public static ImageSource LoadDefault(bool loadFullImage = false) - { - return LoadInternal(Constant.MissingImgIcon, loadFullImage).ImageSource; + return ImageCache.ContainsKey(path) && ImageCache[path] != null; } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); @@ -230,7 +230,7 @@ public static ImageSource Load(string path, bool loadFullImage = false) 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/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 8ce35227f08..511df5e59e5 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -8,18 +8,57 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; - +using Microsoft.FSharp.Core; namespace Flow.Launcher.ViewModel { public class ResultViewModel : BaseModel { + public class LazyAsync : Lazy> + { + private T defaultValue; + + + private readonly Action _updateCallback; + public T Value + { + get + { + if (!IsValueCreated) + { + base.Value.ContinueWith(_ => + { + _updateCallback(); + }); + return defaultValue; + } + else if (!base.Value.IsCompleted) + { + return defaultValue; + } + else 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 Lazy(SetImage); + Image = new LazyAsync(SetImage, ImageLoader.defaultImage, () => + { + OnPropertyChanged(nameof(Image)); + }); } Settings = settings; @@ -39,9 +78,9 @@ public ResultViewModel(Result result, Settings settings) ? Result.SubTitle : Result.SubTitleToolTip; - public Lazy Image { get; set; } + public LazyAsync Image { get; set; } - private ImageSource SetImage() + private async Task SetImage() { var imagePath = Result.IcoPath; if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) @@ -62,14 +101,9 @@ private ImageSource SetImage() return ImageLoader.Load(imagePath); else { - Task.Run(() => - { - Image = new Lazy(() => ImageLoader.Load(imagePath)); - OnPropertyChanged(nameof(Image)); - }); - - return ImageLoader.LoadDefault(); + return await Task.Run(() => ImageLoader.Load(imagePath)); } + } public Result Result { get; } From b3df2fd590bf81883a03f17a955a0d13541ed284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 12:58:53 +0800 Subject: [PATCH 5/8] change default image property name --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 2 +- Flow.Launcher/ViewModel/ResultViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index f61d4615a47..fb2f426a0fe 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -18,7 +18,7 @@ public static class ImageLoader 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)); + public static ImageSource DefaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); private static readonly string[] ImageExtensions = diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 511df5e59e5..f4a51070fd7 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -55,7 +55,7 @@ public ResultViewModel(Result result, Settings settings) if (result != null) { Result = result; - Image = new LazyAsync(SetImage, ImageLoader.defaultImage, () => + Image = new LazyAsync(SetImage, ImageLoader.DefaultImage, () => { OnPropertyChanged(nameof(Image)); }); From dd8a31ccd87bb71d5fc3d3ab74a170e96d2aa0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 25 Nov 2020 23:26:35 +0800 Subject: [PATCH 6/8] Add new to Value Property of the LazyAsync clas --- Flow.Launcher/ViewModel/ResultViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index f4a51070fd7..0909b342f83 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -20,7 +20,7 @@ public class LazyAsync : Lazy> private readonly Action _updateCallback; - public T Value + public new T Value { get { From efcb6a7833e030f9463e6792fdc63579a8c787d5 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 26 Nov 2020 20:47:04 +1100 Subject: [PATCH 7/8] add status check when base.Value has faulted --- .../Image/ImageCache.cs | 6 +++--- .../Image/ImageLoader.cs | 2 -- Flow.Launcher/ViewModel/ResultViewModel.cs | 19 ++++++++----------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 7482ac1cfec..3c819236b5a 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -66,9 +66,10 @@ public ImageSource this[string path] { // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. - foreach (var key in Data.Where(x => x.Key != Constant.MissingImgIcon) - .OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) + .OrderBy(x => x.Value.usage) + .Take(Data.Count - MaxCached) + .Select(x => x.Key)) { if (!(key.Equals(Constant.ErrorIcon) || key.Equals(Constant.DefaultIcon))) { @@ -98,5 +99,4 @@ public int UniqueImagesInCache() 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 fb2f426a0fe..ac333d567b8 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -39,7 +39,6 @@ public static void Initialize() var usage = LoadStorageToConcurrentDictionary(); - foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); @@ -219,7 +218,6 @@ 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); diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 0909b342f83..6e00d3421c5 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -2,13 +2,11 @@ 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; -using Microsoft.FSharp.Core; namespace Flow.Launcher.ViewModel { @@ -18,7 +16,6 @@ public class LazyAsync : Lazy> { private T defaultValue; - private readonly Action _updateCallback; public new T Value { @@ -30,13 +27,14 @@ public class LazyAsync : Lazy> { _updateCallback(); }); + return defaultValue; } - else if (!base.Value.IsCompleted) - { + + if (!base.Value.IsCompleted || base.Value.IsFaulted) return defaultValue; - } - else return base.Value.Result; + + return base.Value.Result; } } public LazyAsync(Func> factory, T defaultValue, Action updateCallback) : base(factory) @@ -45,8 +43,8 @@ public LazyAsync(Func> factory, T defaultValue, Action updateCallback) : { this.defaultValue = defaultValue; } + _updateCallback = updateCallback; - } } @@ -97,13 +95,14 @@ private async Task SetImage() } 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; } @@ -121,7 +120,6 @@ public override bool Equals(object obj) } } - public override int GetHashCode() { return Result.GetHashCode(); @@ -131,6 +129,5 @@ public override string ToString() { return Result.ToString(); } - } } From ac945f48147c8901861bb1ed9f2c9d2d7966e044 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 27 Nov 2020 07:04:39 +1100 Subject: [PATCH 8/8] revert unneeded condition on Constant.MissingImgIcon --- Flow.Launcher.Infrastructure/Image/ImageCache.cs | 5 +---- Flow.Launcher/ViewModel/ResultViewModel.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 3c819236b5a..0cb5779d2b6 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -66,10 +66,7 @@ public ImageSource this[string path] { // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. - foreach (var key in Data.Where(x => x.Key != Constant.MissingImgIcon) - .OrderBy(x => x.Value.usage) - .Take(Data.Count - MaxCached) - .Select(x => x.Key)) + 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))) { diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 6e00d3421c5..00a0e1ae562 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -53,10 +53,14 @@ public ResultViewModel(Result result, Settings settings) if (result != null) { Result = result; - Image = new LazyAsync(SetImage, ImageLoader.DefaultImage, () => - { - OnPropertyChanged(nameof(Image)); - }); + + Image = new LazyAsync( + SetImage, + ImageLoader.DefaultImage, + () => + { + OnPropertyChanged(nameof(Image)); + }); } Settings = settings;