mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into ThemeRedesign
This commit is contained in:
commit
2df24c2790
18 changed files with 225 additions and 127 deletions
|
|
@ -29,13 +29,13 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, manifestFileUrl);
|
var request = new HttpRequestMessage(HttpMethod.Get, manifestFileUrl);
|
||||||
request.Headers.Add("If-None-Match", latestEtag);
|
request.Headers.Add("If-None-Match", latestEtag);
|
||||||
|
|
||||||
var response = await Http.SendAsync(request, token).ConfigureAwait(false);
|
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
if (response.StatusCode == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
Log.Info($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Fetched plugins from manifest repo");
|
Log.Info($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Fetched plugins from manifest repo");
|
||||||
|
|
||||||
var json = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false);
|
await using var json = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false);
|
||||||
|
|
||||||
UserPlugins = await JsonSerializer.DeserializeAsync<List<UserPlugin>>(json, cancellationToken: token).ConfigureAwait(false);
|
UserPlugins = await JsonSerializer.DeserializeAsync<List<UserPlugin>>(json, cancellationToken: token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
@ -56,4 +56,4 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ namespace Flow.Launcher.Core
|
||||||
await updateManager.CreateUninstallerRegistryEntry().ConfigureAwait(false);
|
await updateManager.CreateUninstallerRegistryEntry().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newVersionTips = NewVersinoTips(newReleaseVersion.ToString());
|
var newVersionTips = NewVersionTips(newReleaseVersion.ToString());
|
||||||
|
|
||||||
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
||||||
|
|
||||||
|
|
@ -137,10 +137,10 @@ namespace Flow.Launcher.Core
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string NewVersinoTips(string version)
|
public string NewVersionTips(string version)
|
||||||
{
|
{
|
||||||
var translater = InternationalizationManager.Instance;
|
var translator = InternationalizationManager.Instance;
|
||||||
var tips = string.Format(translater.GetTranslation("newVersionTips"), version);
|
var tips = string.Format(translator.GetTranslation("newVersionTips"), version);
|
||||||
|
|
||||||
return tips;
|
return tips;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ namespace Flow.Launcher.Infrastructure
|
||||||
public const string Logs = "Logs";
|
public const string Logs = "Logs";
|
||||||
|
|
||||||
public const string Website = "https://flowlauncher.com";
|
public const string Website = "https://flowlauncher.com";
|
||||||
|
public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher";
|
||||||
public const string GitHub = "https://github.com/Flow-Launcher/Flow.Launcher";
|
public const string GitHub = "https://github.com/Flow-Launcher/Flow.Launcher";
|
||||||
public const string Docs = "https://flowlauncher.com/docs";
|
public const string Docs = "https://flowlauncher.com/docs";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
||||||
var userName when string.IsNullOrEmpty(userName) =>
|
var userName when string.IsNullOrEmpty(userName) =>
|
||||||
(new Uri($"http://{Proxy.Server}:{Proxy.Port}"), null),
|
(new Uri($"http://{Proxy.Server}:{Proxy.Port}"), null),
|
||||||
_ => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"),
|
_ => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"),
|
||||||
new NetworkCredential(Proxy.UserName, Proxy.Password))
|
new NetworkCredential(Proxy.UserName, Proxy.Password))
|
||||||
},
|
},
|
||||||
_ => (null, null)
|
_ => (null, null)
|
||||||
},
|
},
|
||||||
|
|
@ -79,7 +79,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch(UriFormatException e)
|
catch (UriFormatException e)
|
||||||
{
|
{
|
||||||
API.ShowMsg("Please try again", "Unable to parse Http Proxy");
|
API.ShowMsg("Please try again", "Unable to parse Http Proxy");
|
||||||
Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
|
Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
|
||||||
|
|
@ -94,7 +94,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
if (response.StatusCode == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
|
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
|
||||||
await response.Content.CopyToAsync(fileStream);
|
await response.Content.CopyToAsync(fileStream, token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -117,7 +117,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
||||||
public static Task<string> GetAsync([NotNull] string url, CancellationToken token = default)
|
public static Task<string> GetAsync([NotNull] string url, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
Log.Debug($"|Http.Get|Url <{url}>");
|
Log.Debug($"|Http.Get|Url <{url}>");
|
||||||
return GetAsync(new Uri(url.Replace("#", "%23")), token);
|
return GetAsync(new Uri(url), token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -130,36 +130,57 @@ namespace Flow.Launcher.Infrastructure.Http
|
||||||
{
|
{
|
||||||
Log.Debug($"|Http.Get|Url <{url}>");
|
Log.Debug($"|Http.Get|Url <{url}>");
|
||||||
using var response = await client.GetAsync(url, token);
|
using var response = await client.GetAsync(url, token);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync(token);
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
if (response.StatusCode != HttpStatusCode.OK)
|
||||||
{
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
throw new HttpRequestException(
|
throw new HttpRequestException(
|
||||||
$"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
|
$"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asynchrously get the result as stream from url.
|
/// Send a GET request to the specified Uri with an HTTP completion option and a cancellation token as an asynchronous operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The Uri the request is sent to.</param>
|
||||||
|
/// <param name="completionOption">An HTTP completion option value that indicates when the operation should be considered completed.</param>
|
||||||
|
/// <param name="token">A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task<Stream> GetStreamAsync([NotNull] string url,
|
||||||
|
CancellationToken token = default) => GetStreamAsync(new Uri(url), token);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a GET request to the specified Uri with an HTTP completion option and a cancellation token as an asynchronous operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url"></param>
|
/// <param name="url"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<Stream> GetStreamAsync([NotNull] string url, CancellationToken token = default)
|
public static async Task<Stream> GetStreamAsync([NotNull] Uri url,
|
||||||
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
Log.Debug($"|Http.Get|Url <{url}>");
|
Log.Debug($"|Http.Get|Url <{url}>");
|
||||||
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
return await client.GetStreamAsync(url, token);
|
||||||
return await response.Content.ReadAsStreamAsync();
|
}
|
||||||
|
|
||||||
|
public static async Task<HttpResponseMessage> GetResponseAsync(string url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||||
|
CancellationToken token = default)
|
||||||
|
=> await GetResponseAsync(new Uri(url), completionOption, token);
|
||||||
|
|
||||||
|
public static async Task<HttpResponseMessage> GetResponseAsync([NotNull] Uri url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
Log.Debug($"|Http.Get|Url <{url}>");
|
||||||
|
return await client.GetAsync(url, completionOption, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asynchrously send an HTTP request.
|
/// Asynchrously send an HTTP request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token = default)
|
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
|
return await client.SendAsync(request, completionOption, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,11 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
public class ImageCache
|
public class ImageCache
|
||||||
{
|
{
|
||||||
private const int MaxCached = 50;
|
private const int MaxCached = 50;
|
||||||
public ConcurrentDictionary<string, ImageUsage> Data { get; private set; } = new ConcurrentDictionary<string, ImageUsage>();
|
public ConcurrentDictionary<(string, bool), ImageUsage> Data { get; } = new();
|
||||||
private const int permissibleFactor = 2;
|
private const int permissibleFactor = 2;
|
||||||
private SemaphoreSlim semaphore = new(1, 1);
|
private SemaphoreSlim semaphore = new(1, 1);
|
||||||
|
|
||||||
public void Initialization(Dictionary<string, int> usage)
|
public void Initialization(Dictionary<(string, bool), int> usage)
|
||||||
{
|
{
|
||||||
foreach (var key in usage.Keys)
|
foreach (var key in usage.Keys)
|
||||||
{
|
{
|
||||||
|
|
@ -37,29 +37,29 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageSource this[string path]
|
public ImageSource this[string path, bool isFullImage = false]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Data.TryGetValue(path, out var value))
|
if (!Data.TryGetValue((path, isFullImage), out var value))
|
||||||
{
|
{
|
||||||
value.usage++;
|
return null;
|
||||||
return value.imageSource;
|
|
||||||
}
|
}
|
||||||
|
value.usage++;
|
||||||
|
return value.imageSource;
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
Data.AddOrUpdate(
|
Data.AddOrUpdate(
|
||||||
path,
|
(path, isFullImage),
|
||||||
new ImageUsage(0, value),
|
new ImageUsage(0, value),
|
||||||
(k, v) =>
|
(k, v) =>
|
||||||
{
|
{
|
||||||
v.imageSource = value;
|
v.imageSource = value;
|
||||||
v.usage++;
|
v.usage++;
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
SliceExtra();
|
SliceExtra();
|
||||||
|
|
@ -82,9 +82,9 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsKey(string key)
|
public bool ContainsKey(string key, bool isFullImage)
|
||||||
{
|
{
|
||||||
return key is not null && Data.ContainsKey(key) && Data[key].imageSource != null;
|
return key is not null && Data.ContainsKey((key, isFullImage)) && Data[(key, isFullImage)].imageSource != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CacheSize()
|
public int CacheSize()
|
||||||
|
|
@ -100,4 +100,4 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
return Data.Values.Select(x => x.imageSource).Distinct().Count();
|
return Data.Values.Select(x => x.imageSource).Distinct().Count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,57 +3,58 @@ using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
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
|
||||||
{
|
{
|
||||||
public static class ImageLoader
|
public static class ImageLoader
|
||||||
{
|
{
|
||||||
private static readonly ImageCache ImageCache = new();
|
private static readonly ImageCache ImageCache = new();
|
||||||
private static BinaryStorage<Dictionary<string, int>> _storage;
|
private static BinaryStorage<Dictionary<(string, bool), int>> _storage;
|
||||||
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
|
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
|
||||||
private static IImageHashGenerator _hashGenerator;
|
private static IImageHashGenerator _hashGenerator;
|
||||||
private static readonly bool EnableImageHash = true;
|
private static readonly 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));
|
||||||
|
public const int SmallIconSize = 32;
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
{
|
{
|
||||||
_storage = new BinaryStorage<Dictionary<string, int>>("Image");
|
_storage = new BinaryStorage<Dictionary<(string, bool), int>>("Image");
|
||||||
_hashGenerator = new ImageHashGenerator();
|
_hashGenerator = new ImageHashGenerator();
|
||||||
|
|
||||||
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, false] = 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 ((path, isFullImage), _) in ImageCache.Data)
|
||||||
{
|
{
|
||||||
Load(x.Key);
|
await LoadAsync(path, isFullImage);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
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()}");
|
||||||
});
|
});
|
||||||
|
|
@ -63,17 +64,20 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
{
|
{
|
||||||
lock (_storage)
|
lock (_storage)
|
||||||
{
|
{
|
||||||
_storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, x => x.usage));
|
_storage.Save(ImageCache.Data
|
||||||
|
.ToDictionary(
|
||||||
|
x => x.Key,
|
||||||
|
x => x.Value.usage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConcurrentDictionary<string, int> LoadStorageToConcurrentDictionary()
|
private static ConcurrentDictionary<(string, bool), int> LoadStorageToConcurrentDictionary()
|
||||||
{
|
{
|
||||||
lock (_storage)
|
lock (_storage)
|
||||||
{
|
{
|
||||||
var loaded = _storage.TryLoad(new Dictionary<string, int>());
|
var loaded = _storage.TryLoad(new Dictionary<(string, bool), int>());
|
||||||
|
|
||||||
return new ConcurrentDictionary<string, int>(loaded);
|
return new ConcurrentDictionary<(string, bool), int>(loaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +103,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;
|
||||||
|
|
||||||
|
|
@ -107,13 +111,21 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
return new ImageResult(ImageCache[Constant.MissingImgIcon], ImageType.Error);
|
return new ImageResult(DefaultImage, ImageType.Error);
|
||||||
}
|
|
||||||
if (ImageCache.ContainsKey(path))
|
|
||||||
{
|
|
||||||
return new ImageResult(ImageCache[path], ImageType.Cache);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImageCache.ContainsKey(path, loadFullImage))
|
||||||
|
{
|
||||||
|
return new ImageResult(ImageCache[path, loadFullImage], ImageType.Cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uriResult)
|
||||||
|
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps))
|
||||||
|
{
|
||||||
|
var image = await LoadRemoteImageAsync(loadFullImage, uriResult);
|
||||||
|
ImageCache[path, loadFullImage] = image;
|
||||||
|
return new ImageResult(image, ImageType.ImageFile);
|
||||||
|
}
|
||||||
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var imageSource = new BitmapImage(new Uri(path));
|
var imageSource = new BitmapImage(new Uri(path));
|
||||||
|
|
@ -121,12 +133,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
return new ImageResult(imageSource, ImageType.Data);
|
return new ImageResult(imageSource, ImageType.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Path.IsPathRooted(path))
|
imageResult = await Task.Run(() => GetThumbnailResult(ref path, loadFullImage));
|
||||||
{
|
|
||||||
path = Path.Combine(Constant.ProgramDirectory, "Images", Path.GetFileName(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
imageResult = GetThumbnailResult(ref path, loadFullImage);
|
|
||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -140,14 +147,35 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
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 first try", e);
|
||||||
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2);
|
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2);
|
||||||
|
|
||||||
ImageSource image = ImageCache[Constant.MissingImgIcon];
|
ImageSource image = ImageCache[Constant.MissingImgIcon, false];
|
||||||
ImageCache[path] = image;
|
ImageCache[path, false] = image;
|
||||||
imageResult = new ImageResult(image, ImageType.Error);
|
imageResult = new ImageResult(image, ImageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageResult;
|
return imageResult;
|
||||||
}
|
}
|
||||||
|
private static async Task<BitmapImage> LoadRemoteImageAsync(bool loadFullImage, Uri uriResult)
|
||||||
|
{
|
||||||
|
// Download image from url
|
||||||
|
await using var resp = await GetStreamAsync(uriResult);
|
||||||
|
await using var buffer = new MemoryStream();
|
||||||
|
await resp.CopyToAsync(buffer);
|
||||||
|
buffer.Seek(0, SeekOrigin.Begin);
|
||||||
|
var image = new BitmapImage();
|
||||||
|
image.BeginInit();
|
||||||
|
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
if (!loadFullImage)
|
||||||
|
{
|
||||||
|
image.DecodePixelHeight = SmallIconSize;
|
||||||
|
image.DecodePixelWidth = SmallIconSize;
|
||||||
|
}
|
||||||
|
image.StreamSource = buffer;
|
||||||
|
image.EndInit();
|
||||||
|
image.StreamSource = null;
|
||||||
|
image.Freeze();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
private static ImageResult GetThumbnailResult(ref string path, bool loadFullImage = false)
|
private static ImageResult GetThumbnailResult(ref string path, bool loadFullImage = false)
|
||||||
{
|
{
|
||||||
|
|
@ -192,7 +220,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
image = ImageCache[Constant.MissingImgIcon];
|
image = ImageCache[Constant.MissingImgIcon, false];
|
||||||
path = Constant.MissingImgIcon;
|
path = Constant.MissingImgIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,14 +241,14 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
option);
|
option);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CacheContainImage(string path)
|
public static bool CacheContainImage(string path, bool loadFullImage = false)
|
||||||
{
|
{
|
||||||
return ImageCache.ContainsKey(path) && ImageCache[path] != null;
|
return ImageCache.ContainsKey(path, false) && ImageCache[path, loadFullImage] != 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)
|
||||||
|
|
@ -231,7 +259,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
|
|
||||||
if (GuidToKey.TryGetValue(hash, out string key))
|
if (GuidToKey.TryGetValue(hash, out string key))
|
||||||
{ // image already exists
|
{ // image already exists
|
||||||
img = ImageCache[key] ?? img;
|
img = ImageCache[key, false] ?? img;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // new guid
|
{ // new guid
|
||||||
|
|
@ -240,7 +268,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
// update cache
|
// update cache
|
||||||
ImageCache[path] = img;
|
ImageCache[path, false] = img;
|
||||||
}
|
}
|
||||||
|
|
||||||
return img;
|
return img;
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,14 @@ namespace Flow.Launcher.Plugin
|
||||||
get { return _icoPath; }
|
get { return _icoPath; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(PluginDirectory) && !Path.IsPathRooted(value))
|
// As a standard this property will handle prepping and converting to absolute local path for icon image processing
|
||||||
|
if (!string.IsNullOrEmpty(value)
|
||||||
|
&& !string.IsNullOrEmpty(PluginDirectory)
|
||||||
|
&& !Path.IsPathRooted(value)
|
||||||
|
&& !value.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_icoPath = Path.Combine(value, IcoPath);
|
_icoPath = Path.Combine(PluginDirectory, value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -140,10 +145,11 @@ namespace Flow.Launcher.Plugin
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_pluginDirectory = value;
|
_pluginDirectory = value;
|
||||||
if (!string.IsNullOrEmpty(IcoPath) && !Path.IsPathRooted(IcoPath))
|
|
||||||
{
|
// When the Result object is returned from the query call, PluginDirectory is not provided until
|
||||||
IcoPath = Path.Combine(value, IcoPath);
|
// UpdatePluginMetadata call is made at PluginManager.cs L196. Once the PluginDirectory becomes available
|
||||||
}
|
// we need to update (only if not Uri path) the IcoPath with the full absolute path so the image can be loaded.
|
||||||
|
IcoPath = _icoPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,7 @@
|
||||||
<system:String x:Key="icons">Icons</system:String>
|
<system:String x:Key="icons">Icons</system:String>
|
||||||
<system:String x:Key="about_activate_times">You have activated Flow Launcher {0} times</system:String>
|
<system:String x:Key="about_activate_times">You have activated Flow Launcher {0} times</system:String>
|
||||||
<system:String x:Key="checkUpdates">Check for Updates</system:String>
|
<system:String x:Key="checkUpdates">Check for Updates</system:String>
|
||||||
|
<system:String x:Key="BecomeASponsor">Become A Sponsor</system:String>
|
||||||
<system:String x:Key="newVersionTips">New version {0} is available, would you like to restart Flow Launcher to use the update?</system:String>
|
<system:String x:Key="newVersionTips">New version {0} is available, would you like to restart Flow Launcher to use the update?</system:String>
|
||||||
<system:String x:Key="checkUpdatesFailed">Check updates failed, please check your connection and proxy settings to api.github.com.</system:String>
|
<system:String x:Key="checkUpdatesFailed">Check updates failed, please check your connection and proxy settings to api.github.com.</system:String>
|
||||||
<system:String x:Key="downloadUpdatesFailed">
|
<system:String x:Key="downloadUpdatesFailed">
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Window
|
<Window
|
||||||
x:Class="Flow.Launcher.SettingWindow"
|
x:Class="Flow.Launcher.SettingWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
|
@ -2858,13 +2858,29 @@
|
||||||
Text="{Binding Version}" />
|
Text="{Binding Version}" />
|
||||||
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource version}" />
|
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource version}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button
|
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||||
Grid.Column="1"
|
<Button
|
||||||
Margin="0,0,-14,0"
|
Margin="0,0,10,0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Click="OnCheckUpdates"
|
Click="OnCheckUpdates"
|
||||||
Content="{DynamicResource checkUpdates}"
|
Content="{DynamicResource checkUpdates}" />
|
||||||
Style="{StaticResource AccentButtonStyle}" />
|
<Button
|
||||||
|
Margin="0,0,14,0"
|
||||||
|
Padding="0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Style="{StaticResource AccentButtonStyle}">
|
||||||
|
<Hyperlink
|
||||||
|
NavigateUri="{Binding SponsorPage, Mode=OneWay}"
|
||||||
|
RequestNavigate="OnRequestNavigate"
|
||||||
|
TextDecorations="None">
|
||||||
|
<TextBlock
|
||||||
|
Padding="10,5,10,5"
|
||||||
|
Foreground="White"
|
||||||
|
Text="{DynamicResource BecomeASponsor}" />
|
||||||
|
</Hyperlink>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
<TextBlock Style="{StaticResource Glyph}">
|
<TextBlock Style="{StaticResource Glyph}">
|
||||||

|

|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ using Flow.Launcher.ViewModel;
|
||||||
using ModernWpf;
|
using ModernWpf;
|
||||||
using ModernWpf.Controls;
|
using ModernWpf.Controls;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Security.Policy;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Flow.Launcher.ViewModel
|
||||||
public class ResultViewModel : BaseModel
|
public class ResultViewModel : BaseModel
|
||||||
{
|
{
|
||||||
private static PrivateFontCollection fontCollection = new();
|
private static PrivateFontCollection fontCollection = new();
|
||||||
private static Dictionary<string, string> fonts = new();
|
private static Dictionary<string, string> fonts = new();
|
||||||
|
|
||||||
public ResultViewModel(Result result, Settings settings)
|
public ResultViewModel(Result result, Settings settings)
|
||||||
{
|
{
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -168,12 +168,12 @@ 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, loadFullImage);
|
image = await ImageLoader.LoadAsync(imagePath, loadFullImage);
|
||||||
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, loadFullImage)).ConfigureAwait(false);
|
Image = await ImageLoader.LoadAsync(imagePath, loadFullImage).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result Result { get; }
|
public Result Result { get; }
|
||||||
|
|
|
||||||
|
|
@ -800,6 +800,7 @@ namespace Flow.Launcher.ViewModel
|
||||||
#region about
|
#region about
|
||||||
|
|
||||||
public string Website => Constant.Website;
|
public string Website => Constant.Website;
|
||||||
|
public string SponsorPage => Constant.SponsorPage;
|
||||||
public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest";
|
public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest";
|
||||||
public string Documentation => Constant.Documentation;
|
public string Documentation => Constant.Documentation;
|
||||||
public string Docs => Constant.Docs;
|
public string Docs => Constant.Docs;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const string api = "https://api.bing.com/qsonhs.aspx?q=";
|
const string api = "https://api.bing.com/qsonhs.aspx?q=";
|
||||||
|
|
||||||
using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false);
|
await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false);
|
||||||
|
|
||||||
using var json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token));
|
using var json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token));
|
||||||
var root = json.RootElement.GetProperty("AS");
|
var root = json.RootElement.GetProperty("AS");
|
||||||
|
|
@ -31,16 +31,16 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
||||||
return new List<string>();
|
return new List<string>();
|
||||||
|
|
||||||
return root.GetProperty("Results")
|
return root.GetProperty("Results")
|
||||||
.EnumerateArray()
|
.EnumerateArray()
|
||||||
.SelectMany(r => r.GetProperty("Suggests")
|
.SelectMany(r => r.GetProperty("Suggests")
|
||||||
.EnumerateArray()
|
.EnumerateArray()
|
||||||
.Select(s => s.GetProperty("Txt").GetString()))
|
.Select(s => s.GetProperty("Txt").GetString()))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
|
catch (Exception e) when (e is HttpRequestException or { InnerException: TimeoutException })
|
||||||
{
|
{
|
||||||
Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e);
|
Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -49,7 +49,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
||||||
{
|
{
|
||||||
Log.Exception("|Bing.Suggestions|can't parse suggestions", e);
|
Log.Exception("|Bing.Suggestions|can't parse suggestions", e);
|
||||||
return new List<string>();
|
return new List<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,10 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
||||||
{
|
{
|
||||||
const string api = "https://www.google.com/complete/search?output=chrome&q=";
|
const string api = "https://www.google.com/complete/search?output=chrome&q=";
|
||||||
|
|
||||||
using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query)).ConfigureAwait(false);
|
await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false);
|
||||||
|
|
||||||
using var json = await JsonDocument.ParseAsync(resultStream, cancellationToken: token);
|
using var json = await JsonDocument.ParseAsync(resultStream, cancellationToken: token);
|
||||||
|
|
||||||
if (json == null)
|
|
||||||
return new List<string>();
|
|
||||||
|
|
||||||
var results = json.RootElement.EnumerateArray().ElementAt(1);
|
var results = json.RootElement.EnumerateArray().ElementAt(1);
|
||||||
|
|
||||||
return results.EnumerateArray().Select(o => o.GetString()).ToList();
|
return results.EnumerateArray().Select(o => o.GetString()).ToList();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue