From 1dbc061c2c987a076ee29ba44bc00d7796a4efcb Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Fri, 1 May 2020 11:56:30 +0200 Subject: [PATCH 1/6] Remove parallel loading of icons This is causing null exceptions on Wox startup and will lead to thumbnails not loaded at all. --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 873eb306e..0b23f030f 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -48,10 +48,10 @@ namespace Flow.Launcher.Infrastructure.Image { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { - ImageCache.Usage.AsParallel().ForAll(x => + foreach (string key in _imageCache.Usage.Keys) { - Load(x.Key); - }); + Load(key); + } }); Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); }); From c12dc8e543625feec2ea1e2083adbf9e9b0cc9a1 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Fri, 1 May 2020 11:58:48 +0200 Subject: [PATCH 2/6] Additional code cleanups - Use same variable naming for private members - Remove unnecessary whitespaces --- .../Image/ImageLoader.cs | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 0b23f030f..567cda146 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -13,11 +13,12 @@ namespace Flow.Launcher.Infrastructure.Image { public static class ImageLoader { - private static readonly ImageCache ImageCache = new ImageCache(); - private static BinaryStorage> _storage; - private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); - private static IImageHashGenerator _hashGenerator; + private static readonly ImageCache _imageCache = new ImageCache(); + private static readonly ConcurrentDictionary _guidToKey = new ConcurrentDictionary(); + private static readonly bool _enableHashImage = true; + private static BinaryStorage> _storage; + private static IImageHashGenerator _hashGenerator; private static readonly string[] ImageExtensions = { @@ -30,20 +31,20 @@ namespace Flow.Launcher.Infrastructure.Image ".ico" }; - public static void Initialize() { _storage = new BinaryStorage>("Image"); _hashGenerator = new ImageHashGenerator(); - ImageCache.Usage = LoadStorageToConcurrentDictionary(); + _imageCache.Usage = LoadStorageToConcurrentDictionary(); foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); img.Freeze(); - ImageCache[icon] = img; + _imageCache[icon] = img; } + Task.Run(() => { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => @@ -53,7 +54,7 @@ namespace Flow.Launcher.Infrastructure.Image Load(key); } }); - Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); + Log.Info($"|ImageLoader.Initialize|Number of preload images is <{_imageCache.Usage.Count}>, Images Number: {_imageCache.CacheSize()}, Unique Items {_imageCache.UniqueImagesInCache()}"); }); } @@ -61,7 +62,7 @@ namespace Flow.Launcher.Infrastructure.Image { lock (_storage) { - _storage.Save(ImageCache.CleanupAndToDictionary()); + _storage.Save(_imageCache.CleanupAndToDictionary()); } } @@ -105,11 +106,11 @@ namespace Flow.Launcher.Infrastructure.Image { if (string.IsNullOrEmpty(path)) { - return new ImageResult(ImageCache[Constant.ErrorIcon], ImageType.Error); + return new ImageResult(_imageCache[Constant.ErrorIcon], ImageType.Error); } - if (ImageCache.ContainsKey(path)) + if (_imageCache.ContainsKey(path)) { - return new ImageResult(ImageCache[path], ImageType.Cache); + return new ImageResult(_imageCache[path], ImageType.Cache); } if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) @@ -133,8 +134,11 @@ namespace Flow.Launcher.Infrastructure.Image * - Solution: just load the icon */ type = ImageType.Folder; - image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, - Constant.ThumbnailSize, ThumbnailOptions.IconOnly); + image = WindowsThumbnailProvider.GetThumbnail( + path, + Constant.ThumbnailSize, + Constant.ThumbnailSize, + ThumbnailOptions.IconOnly); } else if (File.Exists(path)) @@ -154,20 +158,26 @@ namespace Flow.Launcher.Infrastructure.Image * be the case in many situations while testing. * - Solution: explicitly pass the ThumbnailOnly flag */ - image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, - Constant.ThumbnailSize, ThumbnailOptions.ThumbnailOnly); + image = WindowsThumbnailProvider.GetThumbnail( + path, + Constant.ThumbnailSize, + Constant.ThumbnailSize, + ThumbnailOptions.ThumbnailOnly); } } else { type = ImageType.File; - image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, - Constant.ThumbnailSize, ThumbnailOptions.None); + image = WindowsThumbnailProvider.GetThumbnail( + path, + Constant.ThumbnailSize, + Constant.ThumbnailSize, + ThumbnailOptions.None); } } else { - image = ImageCache[Constant.ErrorIcon]; + image = _imageCache[Constant.ErrorIcon]; path = Constant.ErrorIcon; } @@ -180,39 +190,40 @@ namespace Flow.Launcher.Infrastructure.Image { Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); type = ImageType.Error; - image = ImageCache[Constant.ErrorIcon]; - ImageCache[path] = image; + image = _imageCache[Constant.ErrorIcon]; + _imageCache[path] = image; } + return new ImageResult(image, type); } - private static bool EnableImageHash = true; - public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); var img = imageResult.ImageSource; if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache) - { // we need to get image hash - string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; + { + // we need to get image hash + string hash = _enableHashImage ? _hashGenerator.GetHashFromImage(img) : null; if (hash != null) { - if (GuidToKey.TryGetValue(hash, out string key)) - { // image already exists - img = ImageCache[key]; + if (_guidToKey.TryGetValue(hash, out string key)) + { + // image already exists + img = _imageCache[key]; } else - { // new guid - GuidToKey[hash] = path; + { + // new guid + _guidToKey[hash] = path; } } // update cache - ImageCache[path] = img; + _imageCache[path] = img; } - return img; } From be3b74d9bb48eadb03426fd63095965632fe4366 Mon Sep 17 00:00:00 2001 From: Ioannis G Date: Sat, 2 May 2020 13:14:51 +0300 Subject: [PATCH 3/6] fix issue extracting file thumbnails in parallel --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 567cda146..e5deea023 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -49,10 +49,10 @@ namespace Flow.Launcher.Infrastructure.Image { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { - foreach (string key in _imageCache.Usage.Keys) + ImageCache.Usage.AsParallel().ForAll(x => { - Load(key); - } + Load(x.Key); + }); }); Log.Info($"|ImageLoader.Initialize|Number of preload images is <{_imageCache.Usage.Count}>, Images Number: {_imageCache.CacheSize()}, Unique Items {_imageCache.UniqueImagesInCache()}"); }); @@ -172,7 +172,7 @@ namespace Flow.Launcher.Infrastructure.Image path, Constant.ThumbnailSize, Constant.ThumbnailSize, - ThumbnailOptions.None); + ThumbnailOptions.ThumbnailOnly); } } else From 1f94b87d360a4fcf352829dc553a72a2b4fd943b Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Sat, 2 May 2020 13:11:39 +0200 Subject: [PATCH 4/6] Fix variable name --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index e5deea023..e27bf0377 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -49,7 +49,7 @@ namespace Flow.Launcher.Infrastructure.Image { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { - ImageCache.Usage.AsParallel().ForAll(x => + _imageCache.Usage.AsParallel().ForAll(x => { Load(x.Key); }); From a3b25d107ca7f970fbbd6b5ac9d14247662c88fa Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Sat, 2 May 2020 13:12:34 +0200 Subject: [PATCH 5/6] Add retry logic for thumbnail loading --- .../Image/ImageLoader.cs | 124 ++++++++++-------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index e27bf0377..8ed43d97f 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -100,8 +100,8 @@ namespace Flow.Launcher.Infrastructure.Image private static ImageResult LoadInternal(string path, bool loadFullImage = false) { - ImageSource image; - ImageType type = ImageType.Error; + ImageResult imageResult; + try { if (string.IsNullOrEmpty(path)) @@ -125,78 +125,94 @@ namespace Flow.Launcher.Infrastructure.Image path = Path.Combine(Constant.ProgramDirectory, "Images", Path.GetFileName(path)); } - if (Directory.Exists(path)) + imageResult = GetThumbnailResult(ref path, loadFullImage); + } + catch (System.Exception e) + { + try { - /* Directories can also have thumbnails instead of shell icons. - * Generating thumbnails for a bunch of folders while scrolling through - * results from Everything makes a big impact on performance and - * Flow.Launcher responsibility. - * - Solution: just load the icon - */ - type = ImageType.Folder; - image = WindowsThumbnailProvider.GetThumbnail( - path, - Constant.ThumbnailSize, - Constant.ThumbnailSize, - ThumbnailOptions.IconOnly); - + // Retry to get thumbnail for certain images when the first try failed + imageResult = GetThumbnailResult(ref path, loadFullImage); } - else if (File.Exists(path)) + catch (System.Exception e2) { - var extension = Path.GetExtension(path).ToLower(); - if (ImageExtensions.Contains(extension)) + Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on first try", e); + Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2); + + ImageSource image = _imageCache[Constant.ErrorIcon]; + _imageCache[path] = image; + imageResult = new ImageResult(image, ImageType.Error); + } + } + + return imageResult; + } + + private static ImageResult GetThumbnailResult(ref string path, bool loadFullImage = false) + { + ImageSource image; + ImageType type = ImageType.Error; + + if (Directory.Exists(path)) + { + /* Directories can also have thumbnails instead of shell icons. + * Generating thumbnails for a bunch of folders while scrolling through + * results from Everything makes a big impact on performance and + * Flow.Launcher responsibility. + * - Solution: just load the icon + */ + type = ImageType.Folder; + image = GetThumbnail(path, ThumbnailOptions.IconOnly); + } + else if (File.Exists(path)) + { + var extension = Path.GetExtension(path).ToLower(); + if (ImageExtensions.Contains(extension)) + { + type = ImageType.ImageFile; + if (loadFullImage) { - type = ImageType.ImageFile; - if (loadFullImage) - { - image = LoadFullImage(path); - } - else - { - /* Although the documentation for GetImage on MSDN indicates that - * if a thumbnail is available it will return one, this has proved to not - * be the case in many situations while testing. - * - Solution: explicitly pass the ThumbnailOnly flag - */ - image = WindowsThumbnailProvider.GetThumbnail( - path, - Constant.ThumbnailSize, - Constant.ThumbnailSize, - ThumbnailOptions.ThumbnailOnly); - } + image = LoadFullImage(path); } else { - type = ImageType.File; - image = WindowsThumbnailProvider.GetThumbnail( - path, - Constant.ThumbnailSize, - Constant.ThumbnailSize, - ThumbnailOptions.ThumbnailOnly); + /* Although the documentation for GetImage on MSDN indicates that + * if a thumbnail is available it will return one, this has proved to not + * be the case in many situations while testing. + * - Solution: explicitly pass the ThumbnailOnly flag + */ + image = GetThumbnail(path, ThumbnailOptions.ThumbnailOnly); } } else { - image = _imageCache[Constant.ErrorIcon]; - path = Constant.ErrorIcon; - } - - if (type != ImageType.Error) - { - image.Freeze(); + type = ImageType.File; + image = GetThumbnail(path, ThumbnailOptions.None); } } - catch (System.Exception e) + else { - Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); - type = ImageType.Error; image = _imageCache[Constant.ErrorIcon]; - _imageCache[path] = image; + path = Constant.ErrorIcon; + } + + if (type != ImageType.Error) + { + image.Freeze(); } return new ImageResult(image, type); } + private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly) + { + return WindowsThumbnailProvider.GetThumbnail( + path, + Constant.ThumbnailSize, + Constant.ThumbnailSize, + option); + } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); From 4e67f28a64691fcc0141c2c04733a104b7f54b2f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 3 May 2020 14:32:52 +1000 Subject: [PATCH 6/6] Update explanation wording --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 8ed43d97f..ed3a6584d 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -131,7 +131,7 @@ namespace Flow.Launcher.Infrastructure.Image { try { - // Retry to get thumbnail for certain images when the first try failed + // Get thumbnail may fail for certain images on the first try, retry again has proven to work imageResult = GetThumbnailResult(ref path, loadFullImage); } catch (System.Exception e2)