From eb5e33aeb0f9c52cd849f2ac2b7455b35adfc5e9 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Thu, 13 Oct 2022 00:32:36 -0500 Subject: [PATCH] Make ImageLoader Async --- Flow.Launcher.Core/Updater.cs | 3 +- .../Image/ImageLoader.cs | 46 ++++++++++--------- Flow.Launcher/Msg.xaml.cs | 16 +++++-- Flow.Launcher/SettingWindow.xaml | 2 +- Flow.Launcher/ViewModel/PluginViewModel.cs | 22 ++++++++- Flow.Launcher/ViewModel/ResultViewModel.cs | 7 +-- .../Programs/Win32.cs | 2 +- .../SearchSourceSetting.xaml.cs | 10 ++-- .../SearchSourceViewModel.cs | 5 +- 9 files changed, 71 insertions(+), 42 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index bad0344eb..96dce8ed9 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -120,7 +120,8 @@ namespace Flow.Launcher.Core var uri = new Uri(repository); var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; - await using var jsonStream = await Http.GetStreamAsync(api).ConfigureAwait(false); + using var response = await Http.GetResponseAsync(api, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + await using var jsonStream = await response.Content.ReadAsStreamAsync(); var releases = await System.Text.Json.JsonSerializer.DeserializeAsync>(jsonStream).ConfigureAwait(false); var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 7a6083706..204e04630 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -4,11 +4,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; +using static Flow.Launcher.Infrastructure.Http.Http; namespace Flow.Launcher.Infrastructure.Image { @@ -24,13 +26,7 @@ namespace Flow.Launcher.Infrastructure.Image private static readonly string[] ImageExtensions = { - ".png", - ".jpg", - ".jpeg", - ".gif", - ".bmp", - ".tiff", - ".ico" + ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" }; public static void Initialize() @@ -40,21 +36,24 @@ namespace Flow.Launcher.Infrastructure.Image var usage = LoadStorageToConcurrentDictionary(); - foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) + foreach (var icon in new[] + { + Constant.DefaultIcon, Constant.MissingImgIcon + }) { ImageSource img = new BitmapImage(new Uri(icon)); img.Freeze(); ImageCache[icon] = img; } - _ = Task.Run(() => + _ = Task.Run(async () => { - Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => + await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () => { - ImageCache.Data.AsParallel().ForAll(x => + foreach (var imageUsage in ImageCache.Data) { - Load(x.Key); - }); + await LoadAsync(imageUsage.Key); + } }); Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); }); @@ -100,7 +99,7 @@ namespace Flow.Launcher.Infrastructure.Image Cache } - private static ImageResult LoadInternal(string path, bool loadFullImage = false) + private static async ValueTask LoadInternalAsync(string path, bool loadFullImage = false) { ImageResult imageResult; @@ -114,21 +113,24 @@ namespace Flow.Launcher.Infrastructure.Image { return new ImageResult(ImageCache[path], ImageType.Cache); } - Uri uriResult; - bool IsUriScheme = Uri.TryCreate(path, UriKind.Absolute, out uriResult) - && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); - if (IsUriScheme) + if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uriResult) + && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps)) { // Download image from url - var resp = Http.Http.GetStreamAsync(path).Result; + await using var resp = await GetStreamAsync(uriResult); if (resp == null) { return new ImageResult(ImageCache[Constant.MissingImgIcon], ImageType.Error); } + await using var buffer = new MemoryStream(); + await resp.CopyToAsync(buffer); + buffer.Seek(0, SeekOrigin.Begin); var image = new BitmapImage(); image.BeginInit(); - image.StreamSource = resp; + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = buffer; image.EndInit(); + image.StreamSource = null; image.Freeze(); ImageCache[path] = image; return new ImageResult(image, ImageType.ImageFile); @@ -237,9 +239,9 @@ namespace Flow.Launcher.Infrastructure.Image return ImageCache.ContainsKey(path) && ImageCache[path] != null; } - public static ImageSource Load(string path, bool loadFullImage = false) + public static async ValueTask LoadAsync(string path, bool loadFullImage = false) { - var imageResult = LoadInternal(path, loadFullImage); + var imageResult = await LoadInternalAsync(path, loadFullImage); var img = imageResult.ImageSource; if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache) diff --git a/Flow.Launcher/Msg.xaml.cs b/Flow.Launcher/Msg.xaml.cs index 6bb2fc2dc..1be89d716 100644 --- a/Flow.Launcher/Msg.xaml.cs +++ b/Flow.Launcher/Msg.xaml.cs @@ -38,10 +38,16 @@ namespace Flow.Launcher Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(TopProperty)); fadeOutStoryboard.Children.Add(fadeOutAnimation); - imgClose.Source = ImageLoader.Load(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\close.png")); + _ = LoadImageAsync(); + imgClose.MouseUp += imgClose_MouseUp; } + private async System.Threading.Tasks.Task LoadImageAsync() + { + imgClose.Source = await ImageLoader.LoadAsync(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\close.png")); + } + void imgClose_MouseUp(object sender, MouseButtonEventArgs e) { if (!closing) @@ -56,7 +62,7 @@ namespace Flow.Launcher Close(); } - public void Show(string title, string subTitle, string iconPath) + public async void Show(string title, string subTitle, string iconPath) { tbTitle.Text = title; tbSubTitle.Text = subTitle; @@ -66,15 +72,15 @@ namespace Flow.Launcher } if (!File.Exists(iconPath)) { - imgIco.Source = ImageLoader.Load(Path.Combine(Constant.ProgramDirectory, "Images\\app.png")); + imgIco.Source = await ImageLoader.LoadAsync(Path.Combine(Constant.ProgramDirectory, "Images\\app.png")); } else { - imgIco.Source = ImageLoader.Load(iconPath); + imgIco.Source = await ImageLoader.LoadAsync(iconPath); } Show(); - Dispatcher.InvokeAsync(async () => + await Dispatcher.InvokeAsync(async () => { if (!closing) { diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 0a63b3a6c..9c9859946 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -1068,7 +1068,7 @@ Height="32" Margin="32,0,0,0" FlowDirection="LeftToRight" - Source="{Binding Image, IsAsync=True}" /> + Source="{Binding Image, Mode=OneWay}" /> ImageLoader.Load(PluginPair.Metadata.IcoPath); + + private async void LoadIconAsync() + { + Image = await ImageLoader.LoadAsync(PluginPair.Metadata.IcoPath); + } + + public ImageSource Image + { + get + { + if (_image == ImageLoader.DefaultImage) + LoadIconAsync(); + + return _image; + } + set => _image = value; + } public bool PluginState { get => !PluginPair.Metadata.Disabled; @@ -50,6 +67,7 @@ namespace Flow.Launcher.ViewModel ? new Control() : settingProvider.CreateSettingPanel() : null; + private ImageSource _image = ImageLoader.DefaultImage; public Visibility ActionKeywordsVisibility => PluginPair.Metadata.ActionKeywords.Count == 1 ? Visibility.Visible : Visibility.Collapsed; public string InitilizaTime => PluginPair.Metadata.InitTime + "ms"; diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index a188cd357..1fd19e3bf 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -145,7 +145,7 @@ namespace Flow.Launcher.ViewModel public GlyphInfo Glyph { get; set; } - private async ValueTask LoadImageAsync() + private async Task LoadImageAsync() { var imagePath = Result.IcoPath; if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) @@ -166,12 +166,13 @@ namespace Flow.Launcher.ViewModel if (ImageLoader.CacheContainImage(imagePath)) { // will get here either when icoPath has value\icon delegate is null\when had exception in delegate - image = ImageLoader.Load(imagePath); + image = await ImageLoader.LoadAsync(imagePath); return; } // We need to modify the property not field here to trigger the OnPropertyChanged event - Image = await Task.Run(() => ImageLoader.Load(imagePath)).ConfigureAwait(false); + var i = await Task.Run(async () => await ImageLoader.LoadAsync(imagePath)); + Image = i; } public Result Result { get; } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 6a8b232e9..e2c0bf9ef 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -100,7 +100,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { Title = title, SubTitle = Main._settings.HideAppsPath ? string.Empty : LnkResolvedPath ?? FullPath, - IcoPath = IcoPath, + IcoPath = "https://media.wired.com/photos/598e35fb99d76447c4eb1f28/master/pass/phonepicutres-TA.jpg", Score = matchResult.Score, TitleHighlightData = matchResult.MatchData, ContextData = this, diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml.cs index fd0701c01..c19396da0 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml.cs @@ -30,7 +30,7 @@ namespace Flow.Launcher.Plugin.WebSearch Initilize(sources, context, Action.Add); } - private void Initilize(IList sources, PluginInitContext context, Action action) + private async void Initilize(IList sources, PluginInitContext context, Action action) { InitializeComponent(); DataContext = _viewModel; @@ -42,7 +42,7 @@ namespace Flow.Launcher.Plugin.WebSearch _viewModel.SetupCustomImagesDirectory(); - imgPreviewIcon.Source = _viewModel.LoadPreviewIcon(_searchSource.IconPath); + imgPreviewIcon.Source = await _viewModel.LoadPreviewIconAsync(_searchSource.IconPath); } private void OnCancelButtonClick(object sender, RoutedEventArgs e) @@ -125,7 +125,7 @@ namespace Flow.Launcher.Plugin.WebSearch } } - private void OnSelectIconClick(object sender, RoutedEventArgs e) + private async void OnSelectIconClick(object sender, RoutedEventArgs e) { const string filter = "Image files (*.jpg, *.jpeg, *.gif, *.png, *.bmp) |*.jpg; *.jpeg; *.gif; *.png; *.bmp"; var dialog = new OpenFileDialog {InitialDirectory = Main.CustomImagesDirectory, Filter = filter}; @@ -140,7 +140,7 @@ namespace Flow.Launcher.Plugin.WebSearch if (_viewModel.ShouldProvideHint(selectedNewIconImageFullPath)) MessageBox.Show(_api.GetTranslation("flowlauncher_plugin_websearch_iconpath_hint")); - imgPreviewIcon.Source = _viewModel.LoadPreviewIcon(selectedNewIconImageFullPath); + imgPreviewIcon.Source = await _viewModel.LoadPreviewIconAsync(selectedNewIconImageFullPath); } } } @@ -151,4 +151,4 @@ namespace Flow.Launcher.Plugin.WebSearch Add, Edit } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceViewModel.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceViewModel.cs index 372e601e0..1b42bf788 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceViewModel.cs @@ -2,6 +2,7 @@ using System; using System.Drawing; using System.IO; +using System.Threading.Tasks; using System.Windows; using System.Windows.Media; @@ -57,9 +58,9 @@ namespace Flow.Launcher.Plugin.WebSearch return Directory.GetParent(fullPathToSelectedImage).ToString() == Main.DefaultImagesDirectory; } - internal ImageSource LoadPreviewIcon(string pathToPreviewIconImage) + internal async ValueTask LoadPreviewIconAsync(string pathToPreviewIconImage) { - return ImageLoader.Load(pathToPreviewIconImage); + return await ImageLoader.LoadAsync(pathToPreviewIconImage); } } }