diff --git a/Flow.Launcher/Images/mainsearch.png b/Doc/mainsearch.png similarity index 100% rename from Flow.Launcher/Images/mainsearch.png rename to Doc/mainsearch.png diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 3dba35f8d..de6ed1b29 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -30,7 +30,7 @@ namespace Flow.Launcher.Infrastructure public static string PythonPath; - public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.png"; + public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.svg"; public const string DefaultTheme = "Darker"; diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 8e2832690..78fa099c9 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -75,23 +75,31 @@ namespace Flow.Launcher.Infrastructure.Http }; } - public static async Task Download([NotNull] string url, [NotNull] string filePath) + public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath) { - using var response = await client.GetAsync(url); - if (response.StatusCode == HttpStatusCode.OK) + try { - await using var fileStream = new FileStream(filePath, FileMode.CreateNew); - await response.Content.CopyToAsync(fileStream); + 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 + { + throw new HttpRequestException($"Error code <{response.StatusCode}> returned from <{url}>"); + } } - else + catch (HttpRequestException e) { - throw new HttpRequestException($"Error code <{response.StatusCode}> returned from <{url}>"); + Log.Exception("Infrastructure.Http", "Http Request Error", e, "DownloadAsync"); + throw; } } /// /// Asynchrously get the result as string from url. - /// When supposing the result is long and large, try using GetStreamAsync to avoid reading as string + /// When supposing the result larger than 83kb, try using GetStreamAsync to avoid reading as string /// /// /// @@ -101,19 +109,33 @@ namespace Flow.Launcher.Infrastructure.Http return GetAsync(new Uri(url.Replace("#", "%23"))); } + /// + /// Asynchrously get the result as string from url. + /// When supposing the result larger than 83kb, try using GetStreamAsync to avoid reading as string + /// + /// + /// public static async Task GetAsync([NotNull] Uri url) { Log.Debug($"|Http.Get|Url <{url}>"); - using var response = await client.GetAsync(url); - var content = await response.Content.ReadAsStringAsync(); - if (response.StatusCode == HttpStatusCode.OK) + try { - 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}>"); + } } - else + catch (HttpRequestException e) { - throw new HttpRequestException( - $"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); + Log.Exception("Infrastructure.Http", "Http Request Error", e, "GetAsync"); + throw; } } @@ -124,9 +146,17 @@ namespace Flow.Launcher.Infrastructure.Http /// public static async Task GetStreamAsync([NotNull] string url) { - Log.Debug($"|Http.Get|Url <{url}>"); - var response = await client.GetAsync(url); - return await response.Content.ReadAsStreamAsync(); + try + { + Log.Debug($"|Http.Get|Url <{url}>"); + var response = await client.GetAsync(url); + return await response.Content.ReadAsStreamAsync(); + } + catch (HttpRequestException e) + { + Log.Exception("Infrastructure.Http", "Http Request Error", e, "GetStreamAsync"); + throw; + } } } } diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 39b2087aa..c3b56b904 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -81,7 +81,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + @@ -93,8 +94,4 @@ - - - - \ No newline at end of file diff --git a/Flow.Launcher/Images/mainsearch.svg b/Flow.Launcher/Images/mainsearch.svg new file mode 100644 index 000000000..5d28abdb3 --- /dev/null +++ b/Flow.Launcher/Images/mainsearch.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index a275a2c16..234fe5091 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -74,7 +74,7 @@ Are you sure you want to delete {0} plugin hotkey? Query window shadow effect Shadow effect has a substantial usage of GPU. - Not recommended if you computer performance is limited. + Not recommended if your computer performance is limited. HTTP Proxy diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 07bb96339..a2cfe569d 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -6,6 +6,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:converters="clr-namespace:Flow.Launcher.Converters" + xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" mc:Ignorable="d" Title="Flow Launcher" Topmost="True" @@ -92,7 +93,8 @@ - + - + diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index a749eae52..af472cf06 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -285,7 +285,7 @@ namespace Flow.Launcher.ViewModel public string OpenResultCommandModifiers { get; private set; } - public ImageSource Image => ImageLoader.Load(Constant.QueryTextBoxIconImagePath); + public string Image => Constant.QueryTextBoxIconImagePath; #endregion diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 00a0e1ae5..4c65f2b9f 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; @@ -12,9 +13,9 @@ namespace Flow.Launcher.ViewModel { public class ResultViewModel : BaseModel { - public class LazyAsync : Lazy> + public class LazyAsync : Lazy> { - private T defaultValue; + private readonly T defaultValue; private readonly Action _updateCallback; public new T Value @@ -23,21 +24,27 @@ namespace Flow.Launcher.ViewModel { if (!IsValueCreated) { - base.Value.ContinueWith(_ => - { - _updateCallback(); - }); + _ = Exercute(); // manually use callback strategy return defaultValue; } - - if (!base.Value.IsCompleted || base.Value.IsFaulted) + + if (!base.Value.IsCompletedSuccessfully) return defaultValue; return base.Value.Result; + + // If none of the variables captured by the local function are captured by other lambdas, + // the compiler can avoid heap allocations. + async ValueTask Exercute() + { + await base.Value.ConfigureAwait(false); + _updateCallback(); + } + } } - public LazyAsync(Func> factory, T defaultValue, Action updateCallback) : base(factory) + public LazyAsync(Func> factory, T defaultValue, Action updateCallback) : base(factory) { if (defaultValue != null) { @@ -55,13 +62,13 @@ namespace Flow.Launcher.ViewModel Result = result; Image = new LazyAsync( - SetImage, + SetImage, ImageLoader.DefaultImage, () => { OnPropertyChanged(nameof(Image)); }); - } + } Settings = settings; } @@ -82,7 +89,7 @@ namespace Flow.Launcher.ViewModel public LazyAsync Image { get; set; } - private async Task SetImage() + private async ValueTask SetImage() { var imagePath = Result.IcoPath; if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) @@ -94,7 +101,7 @@ namespace Flow.Launcher.ViewModel catch (Exception e) { Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); - imagePath = Constant.MissingImgIcon; + return ImageLoader.DefaultImage; } } diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 4ebf898ea..3c90f8712 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -439,7 +439,7 @@ namespace Flow.Launcher.ViewModel } } - public ImageSource ThemeImage => ImageLoader.Load(Constant.QueryTextBoxIconImagePath); + public string ThemeImage => Constant.QueryTextBoxIconImagePath; #endregion diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index 06d8dea7d..1090926fc 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -11,6 +11,7 @@ true false false + en diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index 9d9c09a93..9f0b46d93 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -7,6 +7,7 @@ true true false + en diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj index 0140444e1..cc280b9a9 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -10,6 +10,7 @@ true false false + en diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 724ddf20d..68df5bc1f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -127,7 +127,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); - await Http.Download(plugin.UrlDownload, filePath).ConfigureAwait(false); + await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_download_success")); @@ -217,7 +217,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); - await Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false); + await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false); Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_download_success")); diff --git a/Scripts/post_build.ps1 b/Scripts/post_build.ps1 index 836e27380..b08fac8f6 100644 --- a/Scripts/post_build.ps1 +++ b/Scripts/post_build.ps1 @@ -1,13 +1,13 @@ param( [string]$config = "Release", - [string]$solution, - [string]$targetpath + [string]$solution = (Join-Path $PSScriptRoot ".." -Resolve) ) Write-Host "Config: $config" function Build-Version { if ([string]::IsNullOrEmpty($env:flowVersion)) { - $v = (Get-Command ${TargetPath}).FileVersionInfo.FileVersion + $targetPath = Join-Path $solution "Output/Release/Flow.Launcher.dll" -Resolve + $v = (Get-Command ${targetPath}).FileVersionInfo.FileVersion } else { $v = $env:flowVersion } @@ -31,13 +31,9 @@ function Build-Path { return $p } -function Copy-Resources ($path, $config) { - $project = "$path\Flow.Launcher" - $output = "$path\Output" - $target = "$output\$config" - Copy-Item -Recurse -Force $project\Images\* $target\Images\ +function Copy-Resources ($path) { # making version static as multiple versions can exist in the nuget folder and in the case a breaking change is introduced. - Copy-Item -Force $env:USERPROFILE\.nuget\packages\squirrel.windows\1.5.2\tools\Squirrel.exe $output\Update.exe + Copy-Item -Force $env:USERPROFILE\.nuget\packages\squirrel.windows\1.5.2\tools\Squirrel.exe $path\Output\Update.exe } function Delete-Unused ($path, $config) { @@ -55,17 +51,6 @@ function Validate-Directory ($output) { New-Item $output -ItemType Directory -Force } -function Zip-Release ($path, $version, $output) { - Write-Host "Begin zip release" - - $content = "$path\Output\Release\*" - $zipFile = "$output\Flow-Launcher-v$version.zip" - - Compress-Archive -Force -Path $content -DestinationPath $zipFile - - Write-Host "End zip release" -} - function Pack-Squirrel-Installer ($path, $version, $output) { # msbuild based installer generation is not working in appveyor, not sure why Write-Host "Begin pack squirrel installer" @@ -75,6 +60,8 @@ function Pack-Squirrel-Installer ($path, $version, $output) { Write-Host "Packing: $spec" Write-Host "Input path: $input" + # making version static as multiple versions can exist in the nuget folder and in the case a breaking change is introduced. + New-Alias Nuget $env:USERPROFILE\.nuget\packages\NuGet.CommandLine\5.4.0\tools\NuGet.exe -Force # TODO: can we use dotnet pack here? nuget pack $spec -Version $version -BasePath $input -OutputDirectory $output -Properties Configuration=Release @@ -100,40 +87,30 @@ function Pack-Squirrel-Installer ($path, $version, $output) { Write-Host "End pack squirrel installer" } -function IsDotNetCoreAppSelfContainedPublishEvent{ - return Test-Path $solution\Output\Release\coreclr.dll -} +function Publish-Self-Contained ($p) { -function FixPublishLastWriteDateTimeError ($solutionPath) { - #Fix error from publishing self contained app, when nuget tries to pack core dll references throws the error 'The DateTimeOffset specified cannot be converted into a Zip file timestamp' - gci -path "$solutionPath\Output\Release" -rec -file *.dll | Where-Object {$_.LastWriteTime -lt (Get-Date).AddYears(-20)} | % { try { $_.LastWriteTime = '01/01/2000 00:00:00' } catch {} } + $csproj = Join-Path "$p" "Flow.Launcher/Flow.Launcher.csproj" -Resolve + $profile = Join-Path "$p" "Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml" -Resolve + + # we call dotnet publish on the main project. + # The other projects should have been built in Release at this point. + dotnet publish -c Release $csproj /p:PublishProfile=$profile } function Main { $p = Build-Path $v = Build-Version - Copy-Resources $p $config + Copy-Resources $p if ($config -eq "Release"){ - if(IsDotNetCoreAppSelfContainedPublishEvent) { - FixPublishLastWriteDateTimeError $p - } - Delete-Unused $p $config + + Publish-Self-Contained $p + $o = "$p\Output\Packages" Validate-Directory $o - # making version static as multiple versions can exist in the nuget folder and in the case a breaking change is introduced. - New-Alias Nuget $env:USERPROFILE\.nuget\packages\NuGet.CommandLine\5.4.0\tools\NuGet.exe -Force Pack-Squirrel-Installer $p $v $o - - $isInCI = $env:APPVEYOR - if ($isInCI) { - Zip-Release $p $v $o - } - - Write-Host "List output directory" - Get-ChildItem $o } } diff --git a/appveyor.yml b/appveyor.yml index 1f0937d6d..7d1da7f3f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,17 +26,42 @@ before_build: build: project: Flow.Launcher.sln verbosity: minimal +after_build: + - ps: .\Scripts\post_build.ps1 artifacts: -- path: 'Output\Packages\Flow-Launcher-*.zip' - name: Zip - path: 'Output\Release\Flow.Launcher.Plugin.*.nupkg' name: Plugin nupkg +- path: 'Output\Packages\Flow-Launcher-*.exe' + name: Squirrel Installer +- path: 'Output\Packages\FlowLauncher-*-full.nupkg' + name: Squirrel nupkg +- path: 'Output\Packages\RELEASES' + name: Squirrel RELEASES deploy: - provider: NuGet - artifact: /.*\.nupkg/ - api_key: - secure: n80IeWR3pN81p0w4uXq4mO0TdTXoJSHHFL+yTB9YBJ0Wni2DjZGYwOFdaWzW4hRi - on: - branch: master \ No newline at end of file + - provider: NuGet + artifact: Plugin nupkg + api_key: + secure: n80IeWR3pN81p0w4uXq4mO0TdTXoJSHHFL+yTB9YBJ0Wni2DjZGYwOFdaWzW4hRi + on: + branch: master + + - provider: GitHub + release: v$(flowVersion) + auth_token: + secure: ij4UeXUYQBDJxn2YRAAhUOjklOGVKDB87Hn5J8tKIzj13yatoI7sLM666QDQFEgv + artifact: Squirrel Installer, Squirrel nupkg, Squirrel RELEASES + draft: true + force_update: true + on: + branch: master + + - provider: GitHub + release: v$(flowVersion) + auth_token: + secure: ij4UeXUYQBDJxn2YRAAhUOjklOGVKDB87Hn5J8tKIzj13yatoI7sLM666QDQFEgv + artifact: Squirrel Installer, Squirrel nupkg, Squirrel RELEASES + force_update: true + on: + APPVEYOR_REPO_TAG: true \ No newline at end of file