Make ImageLoader Async

This commit is contained in:
Hongtao Zhang 2022-10-13 00:32:36 -05:00
parent 957c4e237c
commit eb5e33aeb0
9 changed files with 71 additions and 42 deletions

View file

@ -120,7 +120,8 @@ namespace Flow.Launcher.Core
var uri = new Uri(repository); var uri = new Uri(repository);
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; 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<List<GithubRelease>>(jsonStream).ConfigureAwait(false); var releases = await System.Text.Json.JsonSerializer.DeserializeAsync<List<GithubRelease>>(jsonStream).ConfigureAwait(false);
var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First();

View file

@ -4,11 +4,13 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.Storage;
using static Flow.Launcher.Infrastructure.Http.Http;
namespace Flow.Launcher.Infrastructure.Image namespace Flow.Launcher.Infrastructure.Image
{ {
@ -24,13 +26,7 @@ namespace Flow.Launcher.Infrastructure.Image
private static readonly string[] ImageExtensions = private static readonly string[] ImageExtensions =
{ {
".png", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"
".jpg",
".jpeg",
".gif",
".bmp",
".tiff",
".ico"
}; };
public static void Initialize() public static void Initialize()
@ -40,21 +36,24 @@ namespace Flow.Launcher.Infrastructure.Image
var usage = LoadStorageToConcurrentDictionary(); 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)); ImageSource img = new BitmapImage(new Uri(icon));
img.Freeze(); img.Freeze();
ImageCache[icon] = img; 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()}"); 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 Cache
} }
private static ImageResult LoadInternal(string path, bool loadFullImage = false) private static async ValueTask<ImageResult> LoadInternalAsync(string path, bool loadFullImage = false)
{ {
ImageResult imageResult; ImageResult imageResult;
@ -114,21 +113,24 @@ namespace Flow.Launcher.Infrastructure.Image
{ {
return new ImageResult(ImageCache[path], ImageType.Cache); return new ImageResult(ImageCache[path], ImageType.Cache);
} }
Uri uriResult; if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uriResult)
bool IsUriScheme = Uri.TryCreate(path, UriKind.Absolute, out uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps))
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
if (IsUriScheme)
{ {
// Download image from url // Download image from url
var resp = Http.Http.GetStreamAsync(path).Result; await using var resp = await GetStreamAsync(uriResult);
if (resp == null) if (resp == null)
{ {
return new ImageResult(ImageCache[Constant.MissingImgIcon], ImageType.Error); 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(); var image = new BitmapImage();
image.BeginInit(); image.BeginInit();
image.StreamSource = resp; image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = buffer;
image.EndInit(); image.EndInit();
image.StreamSource = null;
image.Freeze(); image.Freeze();
ImageCache[path] = image; ImageCache[path] = image;
return new ImageResult(image, ImageType.ImageFile); return new ImageResult(image, ImageType.ImageFile);
@ -237,9 +239,9 @@ namespace Flow.Launcher.Infrastructure.Image
return ImageCache.ContainsKey(path) && ImageCache[path] != null; return ImageCache.ContainsKey(path) && ImageCache[path] != null;
} }
public static ImageSource Load(string path, bool loadFullImage = false) public static async ValueTask<ImageSource> LoadAsync(string path, bool loadFullImage = false)
{ {
var imageResult = LoadInternal(path, loadFullImage); var imageResult = await LoadInternalAsync(path, loadFullImage);
var img = imageResult.ImageSource; var img = imageResult.ImageSource;
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache) if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)

View file

@ -38,10 +38,16 @@ namespace Flow.Launcher
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(TopProperty)); Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(TopProperty));
fadeOutStoryboard.Children.Add(fadeOutAnimation); fadeOutStoryboard.Children.Add(fadeOutAnimation);
imgClose.Source = ImageLoader.Load(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\close.png")); _ = LoadImageAsync();
imgClose.MouseUp += imgClose_MouseUp; 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) void imgClose_MouseUp(object sender, MouseButtonEventArgs e)
{ {
if (!closing) if (!closing)
@ -56,7 +62,7 @@ namespace Flow.Launcher
Close(); Close();
} }
public void Show(string title, string subTitle, string iconPath) public async void Show(string title, string subTitle, string iconPath)
{ {
tbTitle.Text = title; tbTitle.Text = title;
tbSubTitle.Text = subTitle; tbSubTitle.Text = subTitle;
@ -66,15 +72,15 @@ namespace Flow.Launcher
} }
if (!File.Exists(iconPath)) 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 { else {
imgIco.Source = ImageLoader.Load(iconPath); imgIco.Source = await ImageLoader.LoadAsync(iconPath);
} }
Show(); Show();
Dispatcher.InvokeAsync(async () => await Dispatcher.InvokeAsync(async () =>
{ {
if (!closing) if (!closing)
{ {

View file

@ -1068,7 +1068,7 @@
Height="32" Height="32"
Margin="32,0,0,0" Margin="32,0,0,0"
FlowDirection="LeftToRight" FlowDirection="LeftToRight"
Source="{Binding Image, IsAsync=True}" /> Source="{Binding Image, Mode=OneWay}" />
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Margin="12,0,14,0"> <StackPanel Grid.Column="1" Margin="12,0,14,0">
<TextBlock <TextBlock

View file

@ -1,4 +1,5 @@
using System.Windows; using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Image;
@ -24,7 +25,23 @@ namespace Flow.Launcher.ViewModel
} }
} }
public ImageSource Image => 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 public bool PluginState
{ {
get => !PluginPair.Metadata.Disabled; get => !PluginPair.Metadata.Disabled;
@ -50,6 +67,7 @@ namespace Flow.Launcher.ViewModel
? new Control() ? new Control()
: settingProvider.CreateSettingPanel() : settingProvider.CreateSettingPanel()
: null; : null;
private ImageSource _image = ImageLoader.DefaultImage;
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.ActionKeywords.Count == 1 ? Visibility.Visible : Visibility.Collapsed; public Visibility ActionKeywordsVisibility => PluginPair.Metadata.ActionKeywords.Count == 1 ? Visibility.Visible : Visibility.Collapsed;
public string InitilizaTime => PluginPair.Metadata.InitTime + "ms"; public string InitilizaTime => PluginPair.Metadata.InitTime + "ms";

View file

@ -145,7 +145,7 @@ namespace Flow.Launcher.ViewModel
public GlyphInfo Glyph { get; set; } public GlyphInfo Glyph { get; set; }
private async ValueTask LoadImageAsync() private async Task LoadImageAsync()
{ {
var imagePath = Result.IcoPath; var imagePath = Result.IcoPath;
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
@ -166,12 +166,13 @@ namespace Flow.Launcher.ViewModel
if (ImageLoader.CacheContainImage(imagePath)) if (ImageLoader.CacheContainImage(imagePath))
{ {
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate // 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; return;
} }
// We need to modify the property not field here to trigger the OnPropertyChanged event // 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; } public Result Result { get; }

View file

@ -100,7 +100,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{ {
Title = title, Title = title,
SubTitle = Main._settings.HideAppsPath ? string.Empty : LnkResolvedPath ?? FullPath, 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, Score = matchResult.Score,
TitleHighlightData = matchResult.MatchData, TitleHighlightData = matchResult.MatchData,
ContextData = this, ContextData = this,

View file

@ -30,7 +30,7 @@ namespace Flow.Launcher.Plugin.WebSearch
Initilize(sources, context, Action.Add); Initilize(sources, context, Action.Add);
} }
private void Initilize(IList<SearchSource> sources, PluginInitContext context, Action action) private async void Initilize(IList<SearchSource> sources, PluginInitContext context, Action action)
{ {
InitializeComponent(); InitializeComponent();
DataContext = _viewModel; DataContext = _viewModel;
@ -42,7 +42,7 @@ namespace Flow.Launcher.Plugin.WebSearch
_viewModel.SetupCustomImagesDirectory(); _viewModel.SetupCustomImagesDirectory();
imgPreviewIcon.Source = _viewModel.LoadPreviewIcon(_searchSource.IconPath); imgPreviewIcon.Source = await _viewModel.LoadPreviewIconAsync(_searchSource.IconPath);
} }
private void OnCancelButtonClick(object sender, RoutedEventArgs e) 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"; const string filter = "Image files (*.jpg, *.jpeg, *.gif, *.png, *.bmp) |*.jpg; *.jpeg; *.gif; *.png; *.bmp";
var dialog = new OpenFileDialog {InitialDirectory = Main.CustomImagesDirectory, Filter = filter}; var dialog = new OpenFileDialog {InitialDirectory = Main.CustomImagesDirectory, Filter = filter};
@ -140,7 +140,7 @@ namespace Flow.Launcher.Plugin.WebSearch
if (_viewModel.ShouldProvideHint(selectedNewIconImageFullPath)) if (_viewModel.ShouldProvideHint(selectedNewIconImageFullPath))
MessageBox.Show(_api.GetTranslation("flowlauncher_plugin_websearch_iconpath_hint")); 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, Add,
Edit Edit
} }
} }

View file

@ -2,6 +2,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
@ -57,9 +58,9 @@ namespace Flow.Launcher.Plugin.WebSearch
return Directory.GetParent(fullPathToSelectedImage).ToString() == Main.DefaultImagesDirectory; return Directory.GetParent(fullPathToSelectedImage).ToString() == Main.DefaultImagesDirectory;
} }
internal ImageSource LoadPreviewIcon(string pathToPreviewIconImage) internal async ValueTask<ImageSource> LoadPreviewIconAsync(string pathToPreviewIconImage)
{ {
return ImageLoader.Load(pathToPreviewIconImage); return await ImageLoader.LoadAsync(pathToPreviewIconImage);
} }
} }
} }