Merge branch 'dev' into DotNet5Upgrade

This commit is contained in:
Jeremy Wu 2021-02-23 21:01:39 +11:00
commit f4c77fb67b
82 changed files with 361 additions and 208 deletions

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View file

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View file

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View file

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View file

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View file

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View file

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View file

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View file

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View file

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View file

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 409 B

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

Before

Width:  |  Height:  |  Size: 663 B

After

Width:  |  Height:  |  Size: 663 B

View file

Before

Width:  |  Height:  |  Size: 1,019 B

After

Width:  |  Height:  |  Size: 1,019 B

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -16,7 +16,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Output\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -1,5 +1,8 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
@ -21,17 +24,17 @@ namespace Flow.Launcher.Core.Plugin
};
}
protected override string ExecuteQuery(Query query)
protected override Task<Stream> ExecuteQueryAsync(Query query, CancellationToken token)
{
JsonRPCServerRequestModel request = new JsonRPCServerRequestModel
{
Method = "query",
Parameters = new object[] { query.Search },
Parameters = new object[] {query.Search},
};
_startInfo.Arguments = $"\"{request}\"";
return Execute(_startInfo);
return ExecuteAsync(_startInfo, token);
}
protected override string ExecuteCallback(JsonRPCRequestModel rpcRequest)
@ -40,10 +43,12 @@ namespace Flow.Launcher.Core.Plugin
return Execute(_startInfo);
}
protected override string ExecuteContextMenu(Result selectedResult) {
JsonRPCServerRequestModel request = new JsonRPCServerRequestModel {
protected override string ExecuteContextMenu(Result selectedResult)
{
JsonRPCServerRequestModel request = new JsonRPCServerRequestModel
{
Method = "contextmenu",
Parameters = new object[] { selectedResult.ContextData },
Parameters = new object[] {selectedResult.ContextData},
};
_startInfo.Arguments = $"\"{request}\"";

View file

@ -45,6 +45,8 @@ namespace Flow.Launcher.Core.Plugin
{
[JsonPropertyName("result")]
public new List<JsonRPCResult> Result { get; set; }
public string DebugMessage { get; set; }
}
public class JsonRPCRequestModel : JsonRPCModelBase
@ -58,13 +60,12 @@ namespace Flow.Launcher.Core.Plugin
string rpc = string.Empty;
if (Parameters != null && Parameters.Length > 0)
{
string parameters = Parameters.Aggregate("[", (current, o) => current + (GetParameterByType(o) + ","));
parameters = parameters.Substring(0, parameters.Length - 1) + "]";
rpc = string.Format(@"{{\""method\"":\""{0}\"",\""parameters\"":{1}", Method, parameters);
string parameters = $"[{string.Join(',', Parameters.Select(GetParameterByType))}]";
rpc = $@"{{\""method\"":\""{Method}\"",\""parameters\"":{parameters}";
}
else
{
rpc = string.Format(@"{{\""method\"":\""{0}\"",\""parameters\"":[]", Method);
rpc = $@"{{\""method\"":\""{Method}\"",\""parameters\"":[]";
}
return rpc;
@ -72,26 +73,16 @@ namespace Flow.Launcher.Core.Plugin
}
private string GetParameterByType(object parameter)
=> parameter switch
{
if (parameter == null) {
return "null";
}
if (parameter is string)
{
return string.Format(@"\""{0}\""", ReplaceEscapes(parameter.ToString()));
}
if (parameter is int || parameter is float || parameter is double)
{
return string.Format(@"{0}", parameter);
}
if (parameter is bool)
{
return string.Format(@"{0}", parameter.ToString().ToLower());
}
return parameter.ToString();
}
null => "null",
string _ => $@"\""{ReplaceEscapes(parameter.ToString())}\""",
bool _ => $@"{parameter.ToString().ToLower()}",
_ => parameter.ToString()
};
private string ReplaceEscapes(string str)
private string ReplaceEscapes(string str)
{
return str.Replace(@"\", @"\\") //Escapes in ProcessStartInfo
.Replace(@"\", @"\\") //Escapes itself when passed to client

View file

@ -7,9 +7,9 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flow.Launcher.Infrastructure.Exception;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
using JetBrains.Annotations;
namespace Flow.Launcher.Core.Plugin
{
@ -17,7 +17,7 @@ namespace Flow.Launcher.Core.Plugin
/// Represent the plugin that using JsonPRC
/// every JsonRPC plugin should has its own plugin instance
/// </summary>
internal abstract class JsonRPCPlugin : IPlugin, IContextMenu
internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu
{
protected PluginInitContext context;
public const string JsonRPC = "JsonRPC";
@ -27,24 +27,10 @@ namespace Flow.Launcher.Core.Plugin
/// </summary>
public abstract string SupportedLanguage { get; set; }
protected abstract string ExecuteQuery(Query query);
protected abstract Task<Stream> ExecuteQueryAsync(Query query, CancellationToken token);
protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest);
protected abstract string ExecuteContextMenu(Result selectedResult);
public List<Result> Query(Query query)
{
string output = ExecuteQuery(query);
try
{
return DeserializedResult(output);
}
catch (Exception e)
{
Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e);
return null;
}
}
public List<Result> LoadContextMenus(Result selectedResult)
{
string output = ExecuteContextMenu(selectedResult);
@ -59,50 +45,71 @@ namespace Flow.Launcher.Core.Plugin
}
}
private async Task<List<Result>> DeserializedResultAsync(Stream output)
{
if (output == Stream.Null) return null;
JsonRPCQueryResponseModel queryResponseModel = await
JsonSerializer.DeserializeAsync<JsonRPCQueryResponseModel>(output);
return ParseResults(queryResponseModel);
}
private List<Result> DeserializedResult(string output)
{
if (!String.IsNullOrEmpty(output))
if (string.IsNullOrEmpty(output)) return null;
JsonRPCQueryResponseModel queryResponseModel =
JsonSerializer.Deserialize<JsonRPCQueryResponseModel>(output);
return ParseResults(queryResponseModel);
}
private List<Result> ParseResults(JsonRPCQueryResponseModel queryResponseModel)
{
var results = new List<Result>();
if (queryResponseModel.Result == null) return null;
if(!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
{
List<Result> results = new List<Result>();
context.API.ShowMsg(queryResponseModel.DebugMessage);
}
JsonRPCQueryResponseModel queryResponseModel = JsonSerializer.Deserialize<JsonRPCQueryResponseModel>(output);
if (queryResponseModel.Result == null) return null;
foreach (JsonRPCResult result in queryResponseModel.Result)
foreach (JsonRPCResult result in queryResponseModel.Result)
{
result.Action = c =>
{
JsonRPCResult result1 = result;
result.Action = c =>
{
if (result1.JsonRPCAction == null) return false;
if (result.JsonRPCAction == null) return false;
if (!String.IsNullOrEmpty(result1.JsonRPCAction.Method))
if (!string.IsNullOrEmpty(result.JsonRPCAction.Method))
{
if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher."))
{
if (result1.JsonRPCAction.Method.StartsWith("Flow.Launcher."))
ExecuteFlowLauncherAPI(result.JsonRPCAction.Method.Substring(4),
result.JsonRPCAction.Parameters);
}
else
{
string actionReponse = ExecuteCallback(result.JsonRPCAction);
JsonRPCRequestModel jsonRpcRequestModel =
JsonSerializer.Deserialize<JsonRPCRequestModel>(actionReponse);
if (jsonRpcRequestModel != null
&& !string.IsNullOrEmpty(jsonRpcRequestModel.Method)
&& jsonRpcRequestModel.Method.StartsWith("Flow.Launcher."))
{
ExecuteFlowLauncherAPI(result1.JsonRPCAction.Method.Substring(4), result1.JsonRPCAction.Parameters);
}
else
{
string actionReponse = ExecuteCallback(result1.JsonRPCAction);
JsonRPCRequestModel jsonRpcRequestModel = JsonSerializer.Deserialize<JsonRPCRequestModel>(actionReponse);
if (jsonRpcRequestModel != null
&& !String.IsNullOrEmpty(jsonRpcRequestModel.Method)
&& jsonRpcRequestModel.Method.StartsWith("Flow.Launcher."))
{
ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method.Substring(4), jsonRpcRequestModel.Parameters);
}
ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method.Substring(4),
jsonRpcRequestModel.Parameters);
}
}
return !result1.JsonRPCAction.DontHideAfterAction;
};
results.Add(result);
}
return results;
}
else
{
return null;
}
return !result.JsonRPCAction.DontHideAfterAction;
};
results.Add(result);
}
return results;
}
private void ExecuteFlowLauncherAPI(string method, object[] parameters)
@ -117,9 +124,7 @@ namespace Flow.Launcher.Core.Plugin
catch (Exception)
{
#if (DEBUG)
{
throw;
}
throw;
#endif
}
}
@ -130,17 +135,20 @@ namespace Flow.Launcher.Core.Plugin
/// </summary>
/// <param name="fileName"></param>
/// <param name="arguments"></param>
/// <param name="token">Cancellation Token</param>
/// <returns></returns>
protected string Execute(string fileName, string arguments)
protected Task<Stream> ExecuteAsync(string fileName, string arguments, CancellationToken token = default)
{
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = fileName;
start.Arguments = arguments;
start.UseShellExecute = false;
start.CreateNoWindow = true;
start.RedirectStandardOutput = true;
start.RedirectStandardError = true;
return Execute(start);
ProcessStartInfo start = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
return ExecuteAsync(start, token);
}
protected string Execute(ProcessStartInfo startInfo)
@ -148,52 +156,102 @@ namespace Flow.Launcher.Core.Plugin
try
{
using var process = Process.Start(startInfo);
if (process == null)
{
Log.Error("|JsonRPCPlugin.Execute|Can't start new process");
return string.Empty;
}
if (process == null) return string.Empty;
using var standardOutput = process.StandardOutput;
var result = standardOutput.ReadToEnd();
if (string.IsNullOrEmpty(result))
{
using (var standardError = process.StandardError)
using var standardError = process.StandardError;
var error = standardError.ReadToEnd();
if (!string.IsNullOrEmpty(error))
{
var error = standardError.ReadToEnd();
if (!string.IsNullOrEmpty(error))
{
Log.Error($"|JsonRPCPlugin.Execute|{error}");
return string.Empty;
}
else
{
Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error.");
return string.Empty;
}
Log.Error($"|JsonRPCPlugin.Execute|{error}");
return string.Empty;
}
Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error.");
return string.Empty;
}
else if (result.StartsWith("DEBUG:"))
if (result.StartsWith("DEBUG:"))
{
MessageBox.Show(new Form { TopMost = true }, result.Substring(6));
return string.Empty;
}
else
{
return result;
}
return result;
}
catch (Exception e)
{
Log.Exception($"|JsonRPCPlugin.Execute|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>", e);
Log.Exception(
$"|JsonRPCPlugin.Execute|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>",
e);
return string.Empty;
}
}
public void Init(PluginInitContext ctx)
protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default)
{
context = ctx;
try
{
using var process = Process.Start(startInfo);
if (process == null)
{
Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process");
return Stream.Null;
}
var result = process.StandardOutput.BaseStream;
token.ThrowIfCancellationRequested();
if (!process.StandardError.EndOfStream)
{
using var standardError = process.StandardError;
var error = await standardError.ReadToEndAsync();
if (!string.IsNullOrEmpty(error))
{
Log.Error($"|JsonRPCPlugin.ExecuteAsync|{error}");
return Stream.Null;
}
Log.Error("|JsonRPCPlugin.ExecuteAsync|Empty standard output and standard error.");
return Stream.Null;
}
return result;
}
catch (Exception e)
{
Log.Exception(
$"|JsonRPCPlugin.ExecuteAsync|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>",
e);
return Stream.Null;
}
}
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{
var output = await ExecuteQueryAsync(query, token);
try
{
return await DeserializedResultAsync(output);
}
catch (Exception e)
{
Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e);
return null;
}
}
public Task InitAsync(PluginInitContext context)
{
this.context = context;
return Task.CompletedTask;
}
}
}
}

View file

@ -195,11 +195,11 @@ namespace Flow.Launcher.Core.Plugin
catch (OperationCanceledException)
{
// null will be fine since the results will only be added into queue if the token hasn't been cancelled
return results = null;
return null;
}
catch (Exception e)
{
Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e);
Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e);
}
return results;

View file

@ -1,6 +1,8 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
@ -28,7 +30,7 @@ namespace Flow.Launcher.Core.Plugin
}
protected override string ExecuteQuery(Query query)
protected override Task<Stream> ExecuteQueryAsync(Query query, CancellationToken token)
{
JsonRPCServerRequestModel request = new JsonRPCServerRequestModel
{
@ -40,13 +42,14 @@ namespace Flow.Launcher.Core.Plugin
// todo happlebao why context can't be used in constructor
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
return Execute(_startInfo);
return ExecuteAsync(_startInfo, token);
}
protected override string ExecuteCallback(JsonRPCRequestModel rpcRequest)
{
_startInfo.Arguments = $"-B \"{context.CurrentPluginMetadata.ExecuteFilePath}\" \"{rpcRequest}\"";
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
// TODO: Async Action
return Execute(_startInfo);
}
@ -58,6 +61,7 @@ namespace Flow.Launcher.Core.Plugin
_startInfo.Arguments = $"-B \"{context.CurrentPluginMetadata.ExecuteFilePath}\" \"{request}\"";
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
// TODO: Async Action
return Execute(_startInfo);
}
}

View file

@ -87,7 +87,7 @@ namespace Flow.Launcher.Core
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
}
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException || e is TaskCanceledException)
{
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"),

View file

@ -15,7 +15,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Output\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -9,6 +9,8 @@ using Flow.Launcher.Infrastructure.UserSettings;
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Interop;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.Http
{
@ -18,6 +20,8 @@ namespace Flow.Launcher.Infrastructure.Http
private static HttpClient client = new HttpClient();
public static IPublicAPI API { get; set; }
static Http()
{
// need to be added so it would work on a win10 machine
@ -50,25 +54,36 @@ namespace Flow.Launcher.Infrastructure.Http
/// </summary>
public static void UpdateProxy(ProxyProperty property)
{
(WebProxy.Address, WebProxy.Credentials) = property switch
if (string.IsNullOrEmpty(Proxy.Server))
return;
try
{
ProxyProperty.Enabled => Proxy.Enabled switch
(WebProxy.Address, WebProxy.Credentials) = property switch
{
true => Proxy.UserName switch
ProxyProperty.Enabled => Proxy.Enabled switch
{
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))
true when !string.IsNullOrEmpty(Proxy.Server) => Proxy.UserName switch
{
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))
},
_ => (null, null)
},
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()
};
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()
};
}
catch(UriFormatException e)
{
API.ShowMsg("Please try again", "Unable to parse Http Proxy");
Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
}
}
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default)

View file

@ -34,7 +34,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Output\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -15,7 +15,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -0,0 +1,33 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Infrastructure.Http;
namespace Flow.Launcher.Test
{
[TestFixture]
class HttpTest
{
[Test]
public void GivenHttpProxy_WhenUpdated_ThenWebProxyShouldAlsoBeUpdatedToTheSame()
{
HttpProxy proxy = new HttpProxy();
Http.Proxy = proxy;
proxy.Enabled = true;
proxy.Server = "127.0.0.1";
Assert.AreEqual(Http.WebProxy.Address, new Uri($"http://{proxy.Server}:{proxy.Port}"));
Assert.IsNull(Http.WebProxy.Credentials);
proxy.UserName = "test";
Assert.NotNull(Http.WebProxy.Credentials);
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").UserName, proxy.UserName);
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, "");
proxy.Password = "test password";
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, proxy.Password);
}
}
}

View file

@ -61,7 +61,6 @@ namespace Flow.Launcher
_settingsVM = new SettingWindowViewModel(_updater, _portable);
_settings = _settingsVM.Settings;
Http.Proxy = _settings.Proxy;
_alphabet.Initialize(_settings);
_stringMatcher = new StringMatcher(_alphabet);
@ -71,6 +70,10 @@ namespace Flow.Launcher
PluginManager.LoadPlugins(_settings.PluginSettings);
_mainVM = new MainViewModel(_settings);
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet);
Http.API = API;
Http.Proxy = _settings.Proxy;
await PluginManager.InitializePlugins(API);
var window = new MainWindow(_settings, _mainVM);

View file

@ -17,7 +17,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Output\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -1,16 +1,13 @@
using System;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel
{

View file

@ -15,7 +15,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.BrowserBookmark\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -16,7 +16,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Caculator\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -14,7 +14,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.ControlPanel\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -22,9 +22,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search
this.settings = settings;
}
private class PathEqualityComparator : IEqualityComparer<Result>
{
private static PathEqualityComparator instance;
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
public bool Equals(Result x, Result y)
{
return x.SubTitle == y.SubTitle;
}
public int GetHashCode(Result obj)
{
return obj.SubTitle.GetHashCode();
}
}
internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
{
var results = new List<Result>();
var results = new HashSet<Result>(PathEqualityComparator.Instance);
var querySearch = query.Search;
@ -38,7 +53,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var quickaccessLinks = QuickAccess.AccessLinkListMatched(query, settings.QuickAccessLinks);
if (quickaccessLinks.Count > 0)
results.AddRange(quickaccessLinks);
results.UnionWith(quickaccessLinks);
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
@ -50,9 +65,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
if (!querySearch.IsLocationPathString() && !isEnvironmentVariablePath)
{
results.AddRange(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false));
results.UnionWith(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false));
return results;
return results.ToList();
}
var locationPath = querySearch;
@ -62,7 +77,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
// Check that actual location exists, otherwise directory search will throw directory not found exception
if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath)))
return results;
return results.ToList();
var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath);
@ -79,9 +94,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
token.ThrowIfCancellationRequested();
results.AddRange(directoryResult);
results.UnionWith(directoryResult);
return results;
return results.ToList();
}
private async Task<List<Result>> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, CancellationToken token)

View file

@ -7,7 +7,7 @@
"Name": "Explorer",
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
"Author": "Jeremy Wu",
"Version": "1.7.0",
"Version": "1.7.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",

View file

@ -15,7 +15,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.PluginIndicator\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -233,7 +233,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
Uninstall(x.PluginExistingMetadata);
Uninstall(x.PluginExistingMetadata, false);
var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
$"{x.Name}-{x.NewVersion}.zip");
@ -414,10 +414,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
return Search(results, uninstallSearch);
}
private void Uninstall(PluginMetadata plugin)
private void Uninstall(PluginMetadata plugin, bool removedSetting = true)
{
PluginManager.Settings.Plugins.Remove(plugin.ID);
PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
if (removedSetting)
{
PluginManager.Settings.Plugins.Remove(plugin.ID);
PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
}
// Marked for deletion. Will be deleted on next start up
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));

View file

@ -6,7 +6,7 @@
"Name": "Plugins Manager",
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
"Author": "Jeremy Wu",
"Version": "1.7.0",
"Version": "1.7.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",

View file

@ -16,7 +16,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.ProcessKiller\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -16,7 +16,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Program\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -14,11 +14,12 @@ using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Infrastructure.Logger;
using System.Diagnostics;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
using System.Diagnostics.CodeAnalysis;
namespace Flow.Launcher.Plugin.Program.Programs
{
[Serializable]
public class Win32 : IProgram
public class Win32 : IProgram, IEquatable<Win32>
{
public string Name { get; set; }
public string UniqueIdentifier { get; set; }
@ -35,6 +36,20 @@ namespace Flow.Launcher.Plugin.Program.Programs
private const string ShortcutExtension = "lnk";
private const string ExeExtension = "exe";
private static readonly Win32 Default = new Win32()
{
Name = string.Empty,
Description = string.Empty,
IcoPath = string.Empty,
FullPath = string.Empty,
LnkResolvedPath = null,
ParentDirectory = string.Empty,
ExecutableName = null,
UniqueIdentifier = string.Empty,
Valid = false,
Enabled = false
};
public Result Result(string query, IPublicAPI api)
{
@ -423,12 +438,12 @@ namespace Flow.Launcher.Plugin.Program.Programs
private static Win32 GetProgramFromPath(string path)
{
if (string.IsNullOrEmpty(path))
return null;
return Default;
path = Environment.ExpandEnvironmentVariables(path);
if (!File.Exists(path))
return null;
return Default;
var entry = Win32Program(path);
@ -470,14 +485,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
private static Win32[] ProgramsHasher(IEnumerable<Win32> programs)
private static IEnumerable<Win32> ProgramsHasher(IEnumerable<Win32> programs)
{
return programs.GroupBy(p => p.FullPath.ToLower())
.SelectMany(g =>
{
if (g.Count() > 1)
return DistinctBy(g.Where(p => !string.IsNullOrEmpty(p.Description)), x => x.Description);
return g;
var temp = g.Where(g => !string.IsNullOrEmpty(g.Description)).ToList();
if (temp.Any())
return DistinctBy(temp, x => x.Description);
return g.Take(1);
}).ToArray();
}
@ -489,22 +505,26 @@ namespace Flow.Launcher.Plugin.Program.Programs
var programs = Enumerable.Empty<Win32>();
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes);
programs = programs.Concat(unregistered);
var autoIndexPrograms = Enumerable.Empty<Win32>();
if (settings.EnableRegistrySource)
{
var appPaths = AppPathsPrograms(settings.ProgramSuffixes);
programs = programs.Concat(appPaths);
autoIndexPrograms = autoIndexPrograms.Concat(appPaths);
}
if (settings.EnableStartMenuSource)
{
var startMenu = StartMenuPrograms(settings.ProgramSuffixes);
programs = programs.Concat(startMenu);
autoIndexPrograms = autoIndexPrograms.Concat(startMenu);
}
autoIndexPrograms = ProgramsHasher(autoIndexPrograms);
return ProgramsHasher(programs.Where(p => p != null));
return programs.Concat(autoIndexPrograms).Distinct().ToArray();
}
#if DEBUG //This is to make developer aware of any unhandled exception and add in handling.
catch (Exception e)
@ -522,5 +542,18 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
#endif
}
public override int GetHashCode()
{
return UniqueIdentifier.GetHashCode();
}
public bool Equals([AllowNull] Win32 other)
{
if (other == null)
return false;
return UniqueIdentifier == other.UniqueIdentifier;
}
}
}

View file

@ -4,7 +4,7 @@
"Name": "Program",
"Description": "Search programs in Flow.Launcher",
"Author": "qianlifeng",
"Version": "1.4.2",
"Version": "1.4.3",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",

View file

@ -15,7 +15,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Shell\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -15,7 +15,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Sys\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -15,7 +15,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Url\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -16,7 +16,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.WebSearch\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -123,12 +123,12 @@ namespace Flow.Launcher.Plugin.WebSearch
var source = _settings.SelectedSuggestion;
if (source != null)
{
var suggestions = await source.Suggestions(keyword, token);
var suggestions = await source.Suggestions(keyword, token).ConfigureAwait(false);
if (token.IsCancellationRequested)
return null;
var resultsFromSuggestion = suggestions.Select(o => new Result
var resultsFromSuggestion = suggestions?.Select(o => new Result
{
Title = o,
SubTitle = subtitle,

View file

@ -32,7 +32,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
catch (HttpRequestException e)
{
Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e);
return new List<string>();
return null;
}
if (string.IsNullOrEmpty(result)) return new List<string>();

View file

@ -24,16 +24,13 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
const string api = "https://api.bing.com/qsonhs.aspx?q=";
using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false);
if (resultStream.Length == 0)
return new List<string>(); // this handles the cancellation
json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token)).RootElement.GetProperty("AS");
}
catch (TaskCanceledException)
{
return null;
return new List<string>();
}
catch (HttpRequestException e)
{

View file

@ -24,15 +24,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false);
if (resultStream.Length == 0)
return new List<string>();
json = await JsonDocument.ParseAsync(resultStream);
}
catch (TaskCanceledException)
{
return null;
return new List<string>();
}
catch (HttpRequestException e)
{

View file

@ -4,11 +4,12 @@
</a>
</p>
![Maintenance](https://img.shields.io/maintenance/yes/2021)
![Maintenance](https://img.shields.io/maintenance/yes/3000)
[![Build status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true&retina=true)](https://ci.appveyor.com/project/JohnTheGr8/flow-launcher/branch/dev)
[![Github All Releases](https://img.shields.io/github/downloads/Flow-Launcher/Flow.Launcher/total.svg)](https://github.com/Flow-Launcher/Flow.Launcher/releases)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Flow-Launcher/Flow.Launcher)](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest)
![GitHub Release Date](https://img.shields.io/github/release-date/Flow-Launcher/Flow.Launcher)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Flow-Launcher/Flow.Launcher)](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest)
[![Documentation](https://img.shields.io/badge/Documentation-7389D8)](https://flow-launcher.github.io/docs)
[![Discord](https://img.shields.io/discord/727828229250875472?color=7389D8&labelColor=6A7EC2&label=Community&logo=discord&logoColor=white)](https://discord.gg/AvgAQgh)
Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at being more than an app launcher, it searches, integrates and expands on functionalities. Flow will continue to evolve, designed to be open and built with the community at heart.
@ -16,6 +17,7 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be
<sub>Remember to star it, flow will love you more :)</sub>
## Features
![The Flow](https://user-images.githubusercontent.com/26427004/82151677-fa9c7100-989f-11ea-9143-81de60aaf07d.gif)
- Search everything from applications, files, bookmarks, YouTube, Twitter and more. All from the comfort of your keyboard without ever touching the mouse.
@ -37,27 +39,27 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be
Windows may complain about security due to code not being signed, this will be completed at a later stage. If you downloaded from this repo, you are good to continue the set up.
**Integrations**
- Python plugins:
- Once a Python plugin has been detected at start up, you will be prompted to either select the location or allow Python 3.9.1 to automatic download and install.
### Integrations
- If you use Python plugins:
- Install [Python3](https://www.python.org/downloads/), download `.exe` installer.
- Add Python to `%PATH%` or set it in flow's settings.
- Use `pip` to install `flowlauncher`, open cmd and type `pip install flowlauncher`.
- The Python plugin may require additional modules to be installed, please ensure you check by visiting the plugin's website via `pm install` + plugin name, go to context menu and select `Open website`.
- Start to launch your Python plugins.
- Flow searches files and contents via Windows Index Search, to use Everything, download the plugin [here](https://github.com/Flow-Launcher/Flow.Launcher.Plugin.Everything/releases/latest).
**Usage**
### Usage
- Open flow's search window: <kbd>Alt</kbd>+<kbd>Space</kbd> is the default hotkey.
- Search programs, bookmarks and control panel items with acronyms eg. `gk` or `gp` for GitKraken Preview; fuzzy search eg. `code` or `vis` for Visual Studio Code.
- Open context menu: on the selected result, press <kbd>Ctrl</kbd>+<kbd>O</kbd>/<kbd>Shift</kbd>+<kbd>Enter</kbd>.
- Cancel/Return to previous screen: <kbd>Esc</kbd>.
- Install/Uninstall/Update plugins: in the search window, type `pm install`/`pm uninstall`/`pm update` + the plugin name.
- Press `F5` while in the query window to reload all plugin data.
- Install/Uninstall/Update plugins: in the search window, type `pm` `install`/`uninstall`/`update` + the plugin name.
- Saved user settings are located:
- If using roaming: `%APPDATA%\FlowLauncher`
- If using portable, by default: `%localappdata%\FlowLauncher\app-<VersionOfYourFlowLauncher>\UserData`
- Logs are saved along with your user settings folder.
[More tips](https://flow-launcher.github.io/docs/#/usage-tips)
## Status
Flow is under heavy development, but the code base is stable, so contributions are very welcome. If you would like to help maintain it, please do not hesistate to get in touch.
@ -70,7 +72,7 @@ You will find the main goals of flow placed under Projects board, so feel free t
Get in touch if you like to join the Flow-Launcher Team and help build this great tool.
**Question/Suggestion**
## Question/Suggestion
Yes please, submit an issue to let us know.
@ -86,4 +88,4 @@ Install .Net Core 5 SDK via Visual Studio installer or manually from [here](http
## Documentation
[Wiki](https://github.com/Flow-Launcher/Flow.Launcher/wiki)
Visit [here](https://flow-launcher.github.io/docs) for more information on usage, development and design documentations

View file

@ -16,6 +16,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.7.1")]
[assembly: AssemblyFileVersion("1.7.1")]
[assembly: AssemblyInformationalVersion("1.7.1")]
[assembly: AssemblyVersion("1.7.2")]
[assembly: AssemblyFileVersion("1.7.2")]
[assembly: AssemblyInformationalVersion("1.7.2")]

View file

@ -1,4 +1,4 @@
version: '1.7.1.{build}'
version: '1.7.2.{build}'
init:
- ps: |