mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Make ImageLoader Async
This commit is contained in:
parent
957c4e237c
commit
eb5e33aeb0
9 changed files with 71 additions and 42 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue