diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 20df23e4012..1e4b0453c22 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -8,7 +8,6 @@ using System.Windows; using JetBrains.Annotations; using Squirrel; -using Newtonsoft.Json; using Flow.Launcher.Core.Resource; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Infrastructure; @@ -17,6 +16,7 @@ using System.IO; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using System.Text.Json.Serialization; namespace Flow.Launcher.Core { @@ -29,7 +29,7 @@ public Updater(string gitHubRepository) GitHubRepository = gitHubRepository; } - public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) + public async Task UpdateApp(IPublicAPI api, bool silentUpdate = true) { UpdateManager updateManager; UpdateInfo newUpdateInfo; @@ -39,7 +39,7 @@ public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) try { - updateManager = await GitHubUpdateManager(GitHubRepository); + updateManager = await GitHubUpdateManager(GitHubRepository).ConfigureAwait(false); } catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { @@ -50,7 +50,7 @@ public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) try { // UpdateApp CheckForUpdate will return value only if the app is squirrel installed - newUpdateInfo = await updateManager.CheckForUpdate().NonNull(); + newUpdateInfo = await updateManager.CheckForUpdate().NonNull().ConfigureAwait(false); } catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { @@ -85,8 +85,8 @@ public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) updateManager.Dispose(); return; } - - await updateManager.ApplyReleases(newUpdateInfo); + + await updateManager.ApplyReleases(newUpdateInfo).ConfigureAwait(false); if (DataLocation.PortableDataLocationInUse()) { @@ -98,11 +98,11 @@ public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) } else { - await updateManager.CreateUninstallerRegistryEntry(); + await updateManager.CreateUninstallerRegistryEntry().ConfigureAwait(false); } var newVersionTips = NewVersinoTips(newReleaseVersion.ToString()); - + Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}"); // always dispose UpdateManager @@ -117,13 +117,13 @@ public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) [UsedImplicitly] private class GithubRelease { - [JsonProperty("prerelease")] + [JsonPropertyName("prerelease")] public bool Prerelease { get; [UsedImplicitly] set; } - [JsonProperty("published_at")] + [JsonPropertyName("published_at")] public DateTime PublishedAt { get; [UsedImplicitly] set; } - [JsonProperty("html_url")] + [JsonPropertyName("html_url")] public string HtmlUrl { get; [UsedImplicitly] set; } } @@ -133,13 +133,13 @@ private async Task GitHubUpdateManager(string repository) var uri = new Uri(repository); var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; - var json = await Http.Get(api); + var jsonStream = await Http.GetStreamAsync(api).ConfigureAwait(false); - var releases = JsonConvert.DeserializeObject>(json); + var releases = await System.Text.Json.JsonSerializer.DeserializeAsync>(jsonStream).ConfigureAwait(false); var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/"); - var client = new WebClient { Proxy = Http.WebProxy() }; + var client = new WebClient { Proxy = Http.WebProxy }; var downloader = new FileDownloader(client); var manager = new UpdateManager(latestUrl, urlDownloader: downloader); diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 9d2fe7860eb..8e2832690e4 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -6,6 +6,8 @@ using JetBrains.Annotations; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; +using System; +using System.ComponentModel; namespace Flow.Launcher.Infrastructure.Http { @@ -13,6 +15,14 @@ public static class Http { private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko"; + private static HttpClient client; + + private static SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler() + { + UseProxy = true, + Proxy = WebProxy + }; + static Http() { // need to be added so it would work on a win10 machine @@ -20,71 +30,103 @@ static Http() ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; + + client = new HttpClient(socketsHttpHandler, false); + client.DefaultRequestHeaders.Add("User-Agent", UserAgent); } - public static HttpProxy Proxy { private get; set; } - public static IWebProxy WebProxy() + private static HttpProxy proxy; + + public static HttpProxy Proxy { - if (Proxy != null && Proxy.Enabled && !string.IsNullOrEmpty(Proxy.Server)) + private get { return proxy; } + set { - if (string.IsNullOrEmpty(Proxy.UserName) || string.IsNullOrEmpty(Proxy.Password)) - { - var webProxy = new WebProxy(Proxy.Server, Proxy.Port); - return webProxy; - } - else + proxy = value; + proxy.PropertyChanged += UpdateProxy; + } + } + + public static WebProxy WebProxy { get; } = new WebProxy(); + + /// + /// Update the Address of the Proxy to modify the client Proxy + /// + public static void UpdateProxy(ProxyProperty property) + { + (WebProxy.Address, WebProxy.Credentials) = property switch + { + ProxyProperty.Enabled => Proxy.Enabled switch { - var webProxy = new WebProxy(Proxy.Server, Proxy.Port) + true => Proxy.UserName switch { - Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password) - }; - return webProxy; - } + var userName when !string.IsNullOrEmpty(userName) => + (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), null), + _ => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), + new NetworkCredential(Proxy.UserName, Proxy.Password)) + }, + false => (null, null) + }, + ProxyProperty.Server => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials), + ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials), + ProxyProperty.UserName => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), + ProxyProperty.Password => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), + _ => throw new ArgumentOutOfRangeException() + }; + } + + public static async Task Download([NotNull] string url, [NotNull] string filePath) + { + using var response = await client.GetAsync(url); + if (response.StatusCode == HttpStatusCode.OK) + { + await using var fileStream = new FileStream(filePath, FileMode.CreateNew); + await response.Content.CopyToAsync(fileStream); } else { - return WebRequest.GetSystemWebProxy(); + throw new HttpRequestException($"Error code <{response.StatusCode}> returned from <{url}>"); } } - public static void Download([NotNull] string url, [NotNull] string filePath) + /// + /// Asynchrously get the result as string from url. + /// When supposing the result is long and large, try using GetStreamAsync to avoid reading as string + /// + /// + /// + public static Task GetAsync([NotNull] string url) { - var client = new WebClient { Proxy = WebProxy() }; - client.Headers.Add("user-agent", UserAgent); - client.DownloadFile(url, filePath); + Log.Debug($"|Http.Get|Url <{url}>"); + return GetAsync(new Uri(url.Replace("#", "%23"))); } - public static async Task Get([NotNull] string url, string encoding = "UTF-8") + public static async Task GetAsync([NotNull] Uri url) { Log.Debug($"|Http.Get|Url <{url}>"); - var request = WebRequest.CreateHttp(url); - request.Method = "GET"; - request.Timeout = 6000; - request.Proxy = WebProxy(); - request.UserAgent = UserAgent; - var response = await request.GetResponseAsync() as HttpWebResponse; - response = response.NonNull(); - var stream = response.GetResponseStream().NonNull(); - - using var reader = new StreamReader(stream, Encoding.GetEncoding(encoding)); - var content = await reader.ReadToEndAsync(); - if (response.StatusCode != HttpStatusCode.OK) - throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); - - return content; + using var response = await client.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + return content; + } + else + { + throw new HttpRequestException( + $"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); + } } + /// + /// Asynchrously get the result as stream from url. + /// + /// + /// public static async Task GetStreamAsync([NotNull] string url) { Log.Debug($"|Http.Get|Url <{url}>"); - var request = WebRequest.CreateHttp(url); - request.Method = "GET"; - request.Timeout = 6000; - request.Proxy = WebProxy(); - request.UserAgent = UserAgent; - var response = await request.GetResponseAsync() as HttpWebResponse; - response = response.NonNull(); - return response.GetResponseStream().NonNull(); + var response = await client.GetAsync(url); + return await response.Content.ReadAsStreamAsync(); } } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/HttpProxy.cs b/Flow.Launcher.Infrastructure/UserSettings/HttpProxy.cs index c1b0c1dd7fe..21319352633 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/HttpProxy.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/HttpProxy.cs @@ -1,11 +1,80 @@ -namespace Flow.Launcher.Infrastructure.UserSettings +using System.ComponentModel; + +namespace Flow.Launcher.Infrastructure.UserSettings { + public enum ProxyProperty + { + Enabled, + Server, + Port, + UserName, + Password + } + public class HttpProxy { - public bool Enabled { get; set; } = false; - public string Server { get; set; } - public int Port { get; set; } - public string UserName { get; set; } - public string Password { get; set; } + private bool _enabled = false; + private string _server; + private int _port; + private string _userName; + private string _password; + + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + OnPropertyChanged(ProxyProperty.Enabled); + } + } + + public string Server + { + get => _server; + set + { + _server = value; + OnPropertyChanged(ProxyProperty.Server); + } + } + + public int Port + { + get => _port; + set + { + _port = value; + OnPropertyChanged(ProxyProperty.Port); + } + } + + public string UserName + { + get => _userName; + set + { + _userName = value; + OnPropertyChanged(ProxyProperty.UserName); + } + } + + public string Password + { + get => _password; + set + { + _password = value; + OnPropertyChanged(ProxyProperty.Password); + } + } + + public delegate void ProxyPropertyChangedHandler(ProxyProperty property); + public event ProxyPropertyChangedHandler PropertyChanged; + + private void OnPropertyChanged(ProxyProperty property) + { + PropertyChanged?.Invoke(property); + } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 716a424ff1b..d700b9dfd27 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -22,7 +22,7 @@ public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI1 internal PluginsManager pluginManager; - private DateTime lastUpdateTime; + private DateTime lastUpdateTime = DateTime.MinValue; public Control CreateSettingPanel() { diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs index ad818562096..814e0764df7 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -10,6 +10,7 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models internal class PluginsManifest { internal List UserPlugins { get; private set; } + internal PluginsManifest() { Task.Run(async () => await DownloadManifest()).Wait(); @@ -30,7 +31,6 @@ internal async Task DownloadManifest() UserPlugins = new List(); } - } } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 9635648d480..ac15618ca76 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -87,12 +87,12 @@ internal List GetDefaultHotKeys() }; } - internal void InstallOrUpdate(UserPlugin plugin) + internal async Task InstallOrUpdate(UserPlugin plugin) { if (PluginExists(plugin.ID)) { if (Context.API.GetAllPlugins() - .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version != plugin.Version)) + .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0)) { if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), Context.API.GetTranslation("plugin_pluginsmanager_update_title"), @@ -127,7 +127,7 @@ internal void InstallOrUpdate(UserPlugin plugin) Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); - Http.Download(plugin.UrlDownload, filePath); + await Http.Download(plugin.UrlDownload, filePath).ConfigureAwait(false); Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_download_success")); @@ -140,11 +140,8 @@ internal void InstallOrUpdate(UserPlugin plugin) Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); } - Application.Current.Dispatcher.Invoke(() => - { - Install(plugin, filePath); - Context.API.RestartApp(); - }); + Install(plugin, filePath); + Context.API.RestartApp(); } internal List RequestUpdate(string search) @@ -211,10 +208,14 @@ on existingPlugin.Metadata.ID equals pluginFromManifest.ID var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, $"{x.Name}-{x.NewVersion}.zip"); - Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath); - Install(x.PluginNewUserPlugin, downloadToFilePath); - Context.API.RestartApp(); + Task.Run(async delegate + { + await Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false); + Install(x.PluginNewUserPlugin, downloadToFilePath); + + Context.API.RestartApp(); + }); return true; } @@ -264,8 +265,7 @@ internal List RequestInstallOrUpdate(string searchName) Action = e => { Application.Current.MainWindow.Hide(); - InstallOrUpdate(x); - + _ = InstallOrUpdate(x); // No need to wait return ShouldHideWindow; }, ContextData = x diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index 57db223bcb9..6772acf8256 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json.Linq; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; +using System.Net.Http; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { @@ -22,9 +23,9 @@ public override async Task> Suggestions(string query) try { const string api = "http://suggestion.baidu.com/su?json=1&wd="; - result = await Http.Get(api + Uri.EscapeUriString(query), "GB2312"); + result = await Http.GetAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); } - catch (WebException e) + catch (HttpRequestException e) { Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e); return new List(); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 81878bd8b4a..5b9538091b9 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json.Linq; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; +using System.Net.Http; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { @@ -18,13 +19,12 @@ public override async Task> Suggestions(string query) try { const string api = "https://www.google.com/complete/search?output=chrome&q="; - result = await Http.Get(api + Uri.EscapeUriString(query)); + result = await Http.GetAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); } - catch (WebException e) + catch (HttpRequestException e) { Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); return new List(); - ; } if (string.IsNullOrEmpty(result)) return new List(); JContainer json;