diff --git a/Plugins/Wox.Plugin.Calculator/DecimalSeparator.cs b/Plugins/Wox.Plugin.Calculator/DecimalSeparator.cs new file mode 100644 index 000000000..5aad9b39a --- /dev/null +++ b/Plugins/Wox.Plugin.Calculator/DecimalSeparator.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Wox.Core; + +namespace Wox.Plugin.Caculator +{ + [TypeConverter(typeof(LocalizationConverter))] + public enum DecimalSeparator + { + [LocalizedDescription("wox_plugin_calculator_decimal_seperator_use_system_locale")] + UseSystemLocale, + + [LocalizedDescription("wox_plugin_calculator_decimal_seperator_dot")] + Dot, + + [LocalizedDescription("wox_plugin_calculator_decimal_seperator_comma")] + Comma + } +} \ No newline at end of file diff --git a/Plugins/Wox.Plugin.Calculator/Languages/de.xaml b/Plugins/Wox.Plugin.Calculator/Languages/de.xaml index 286e4bc9b..eb6847af0 100644 --- a/Plugins/Wox.Plugin.Calculator/Languages/de.xaml +++ b/Plugins/Wox.Plugin.Calculator/Languages/de.xaml @@ -7,4 +7,10 @@ Keine Zahl (NaN) Ausdruck falsch oder nicht vollständig (Klammern vergessen?) Diese Zahl in die Zwischenablage kopieren + Dezimaltrennzeichen + Das Dezimaltrennzeichen, welches bei der Ausgabe verwendet werden soll. + Systemeinstellung nutzen + Komma (,) + Punkt (.) + Max. Nachkommastellen \ No newline at end of file diff --git a/Plugins/Wox.Plugin.Calculator/Languages/en.xaml b/Plugins/Wox.Plugin.Calculator/Languages/en.xaml index 3b5fa32b3..0df0d3342 100644 --- a/Plugins/Wox.Plugin.Calculator/Languages/en.xaml +++ b/Plugins/Wox.Plugin.Calculator/Languages/en.xaml @@ -7,4 +7,10 @@ Not a number (NaN) Expression wrong or incomplete (Did you forget some parentheses?) Copy this number to the clipboard + Decimal separator + The decimal separator to be used in the output. + Use system locale + Comma (,) + Dot (.) + Max. decimal places \ No newline at end of file diff --git a/Plugins/Wox.Plugin.Calculator/Main.cs b/Plugins/Wox.Plugin.Calculator/Main.cs index fab0b8e03..f2f7f7e18 100644 --- a/Plugins/Wox.Plugin.Calculator/Main.cs +++ b/Plugins/Wox.Plugin.Calculator/Main.cs @@ -1,12 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Windows; +using System.Windows.Controls; using Mages.Core; +using Wox.Infrastructure.Storage; +using Wox.Plugin.Caculator.ViewModels; +using Wox.Plugin.Caculator.Views; namespace Wox.Plugin.Caculator { - public class Main : IPlugin, IPluginI18n + public class Main : IPlugin, IPluginI18n, ISavable, ISettingProvider { private static readonly Regex RegValidExpressChar = new Regex( @"^(" + @@ -21,20 +27,33 @@ namespace Wox.Plugin.Caculator private static readonly Engine MagesEngine; private PluginInitContext Context { get; set; } + private static Settings _settings; + private static SettingsViewModel _viewModel; + static Main() { - MagesEngine = new Engine(); + MagesEngine = new Engine(); + } + + public void Init(PluginInitContext context) + { + Context = context; + + _viewModel = new SettingsViewModel(); + _settings = _viewModel.Settings; } public List Query(Query query) { - if (query.Search.Length <= 2 // don't affect when user only input "e" or "i" keyword - || !RegValidExpressChar.IsMatch(query.Search) - || !IsBracketComplete(query.Search)) return new List(); + if (!CanCalculate(query)) + { + return new List(); + } try { - var result = MagesEngine.Interpret(query.Search); + var expression = query.Search.Replace(",", "."); + var result = MagesEngine.Interpret(expression); if (result.ToString() == "NaN") result = Context.API.GetTranslation("wox_plugin_calculator_not_a_number"); @@ -42,14 +61,16 @@ namespace Wox.Plugin.Caculator if (result is Function) result = Context.API.GetTranslation("wox_plugin_calculator_expression_not_complete"); - if (!string.IsNullOrEmpty(result?.ToString())) { + decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero); + string newResult = ChangeDecimalSeparator(roundedResult, GetDecimalSeparator()); + return new List { new Result { - Title = result.ToString(), + Title = newResult, IcoPath = "Images/calculator.png", Score = 300, SubTitle = Context.API.GetTranslation("wox_plugin_calculator_copy_number_to_clipboard"), @@ -57,7 +78,7 @@ namespace Wox.Plugin.Caculator { try { - Clipboard.SetText(result.ToString()); + Clipboard.SetText(newResult); return true; } catch (ExternalException) @@ -78,6 +99,53 @@ namespace Wox.Plugin.Caculator return new List(); } + private bool CanCalculate(Query query) + { + // Don't execute when user only input "e" or "i" keyword + if (query.Search.Length < 2) + { + return false; + } + + if (!RegValidExpressChar.IsMatch(query.Search)) + { + return false; + } + + if (!IsBracketComplete(query.Search)) + { + return false; + } + + return true; + } + + private string ChangeDecimalSeparator(decimal value, string newDecimalSeparator) + { + if (String.IsNullOrEmpty(newDecimalSeparator)) + { + return value.ToString(); + } + + var numberFormatInfo = new NumberFormatInfo + { + NumberDecimalSeparator = newDecimalSeparator + }; + return value.ToString(numberFormatInfo); + } + + private string GetDecimalSeparator() + { + string systemDecimalSeperator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + switch (_settings.DecimalSeparator) + { + case DecimalSeparator.UseSystemLocale: return systemDecimalSeperator; + case DecimalSeparator.Dot: return "."; + case DecimalSeparator.Comma: return ","; + default: return systemDecimalSeperator; + } + } + private bool IsBracketComplete(string query) { var matchs = RegBrackets.Matches(query); @@ -96,12 +164,7 @@ namespace Wox.Plugin.Caculator return leftBracketCount == 0; } - - public void Init(PluginInitContext context) - { - Context = context; - } - + public string GetTranslatedPluginTitle() { return Context.API.GetTranslation("wox_plugin_caculator_plugin_name"); @@ -111,5 +174,15 @@ namespace Wox.Plugin.Caculator { return Context.API.GetTranslation("wox_plugin_caculator_plugin_description"); } + + public Control CreateSettingPanel() + { + return new CalculatorSettings(_viewModel); + } + + public void Save() + { + _viewModel.Save(); + } } } diff --git a/Plugins/Wox.Plugin.Calculator/Settings.cs b/Plugins/Wox.Plugin.Calculator/Settings.cs new file mode 100644 index 000000000..ce01f2da0 --- /dev/null +++ b/Plugins/Wox.Plugin.Calculator/Settings.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Wox.Plugin.Caculator +{ + public class Settings + { + public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale; + public int MaxDecimalPlaces { get; set; } = 10; + } +} diff --git a/Plugins/Wox.Plugin.Calculator/ViewModels/SettingsViewModel.cs b/Plugins/Wox.Plugin.Calculator/ViewModels/SettingsViewModel.cs new file mode 100644 index 000000000..8550f47a5 --- /dev/null +++ b/Plugins/Wox.Plugin.Calculator/ViewModels/SettingsViewModel.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Wox.Infrastructure.Storage; +using Wox.Infrastructure.UserSettings; + +namespace Wox.Plugin.Caculator.ViewModels +{ + public class SettingsViewModel : BaseModel, ISavable + { + private readonly PluginJsonStorage _storage; + + public SettingsViewModel() + { + _storage = new PluginJsonStorage(); + Settings = _storage.Load(); + } + + public Settings Settings { get; set; } + + public IEnumerable MaxDecimalPlacesRange => Enumerable.Range(1, 20); + + public void Save() + { + _storage.Save(); + } + } +} diff --git a/Plugins/Wox.Plugin.Calculator/Views/CalculatorSettings.xaml b/Plugins/Wox.Plugin.Calculator/Views/CalculatorSettings.xaml new file mode 100644 index 000000000..183f10e5f --- /dev/null +++ b/Plugins/Wox.Plugin.Calculator/Views/CalculatorSettings.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plugins/Wox.Plugin.Calculator/Views/CalculatorSettings.xaml.cs b/Plugins/Wox.Plugin.Calculator/Views/CalculatorSettings.xaml.cs new file mode 100644 index 000000000..c15bdda77 --- /dev/null +++ b/Plugins/Wox.Plugin.Calculator/Views/CalculatorSettings.xaml.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Wox.Plugin.Caculator.ViewModels; + +namespace Wox.Plugin.Caculator.Views +{ + /// + /// Interaction logic for CalculatorSettings.xaml + /// + public partial class CalculatorSettings : UserControl + { + private readonly SettingsViewModel _viewModel; + private readonly Settings _settings; + + public CalculatorSettings(SettingsViewModel viewModel) + { + _viewModel = viewModel; + _settings = viewModel.Settings; + DataContext = viewModel; + InitializeComponent(); + } + + private void CalculatorSettings_Loaded(object sender, RoutedEventArgs e) + { + DecimalSeparatorComboBox.SelectedItem = _settings.DecimalSeparator; + MaxDecimalPlaces.SelectedItem = _settings.MaxDecimalPlaces; + } + } + + +} diff --git a/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj b/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj index b0a091934..f24ad118e 100644 --- a/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj +++ b/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj @@ -46,14 +46,22 @@ + + Properties\SolutionAssemblyInfo.cs + + + + + CalculatorSettings.xaml + @@ -61,6 +69,10 @@ + + {B749F0DB-8E75-47DB-9E5E-265D16D0C0D2} + Wox.Core + {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3} Wox.Infrastructure @@ -122,12 +134,18 @@ 10.3.0 - 1.5.0 + 1.6.0 4.0.0 + + + Designer + MSBuild:Compile + + diff --git a/Plugins/Wox.Plugin.PluginIndicator/Main.cs b/Plugins/Wox.Plugin.PluginIndicator/Main.cs index 5f9ec07be..54dcf007a 100644 --- a/Plugins/Wox.Plugin.PluginIndicator/Main.cs +++ b/Plugins/Wox.Plugin.PluginIndicator/Main.cs @@ -13,8 +13,7 @@ namespace Wox.Plugin.PluginIndicator var results = from keyword in PluginManager.NonGlobalPlugins.Keys where keyword.StartsWith(query.Terms[0]) let metadata = PluginManager.NonGlobalPlugins[keyword].Metadata - let disabled = PluginManager.Settings.Plugins[metadata.ID].Disabled - where !disabled + where !metadata.Disabled select new Result { Title = keyword, diff --git a/Plugins/Wox.Plugin.Sys/Images/checkupdate.png b/Plugins/Wox.Plugin.Sys/Images/checkupdate.png new file mode 100644 index 000000000..955f6fdbb Binary files /dev/null and b/Plugins/Wox.Plugin.Sys/Images/checkupdate.png differ diff --git a/Plugins/Wox.Plugin.Sys/Main.cs b/Plugins/Wox.Plugin.Sys/Main.cs index e888dbb37..f6be3a435 100644 --- a/Plugins/Wox.Plugin.Sys/Main.cs +++ b/Plugins/Wox.Plugin.Sys/Main.cs @@ -234,6 +234,20 @@ namespace Wox.Plugin.Sys context.API.GetTranslation("wox_plugin_sys_dlgtext_all_applicableplugins_reloaded")); return true; } + }, + new Result + { + Title = "Check For Update", + SubTitle = "Check for new Wox update", + IcoPath = "Images\\checkupdate.png", + Action = c => + { + Application.Current.MainWindow.Hide(); + context.API.CheckForNewUpdate(); + context.API.ShowMsg("Please wait...", + "Checking for new update"); + return true; + } } }); return results; diff --git a/Plugins/Wox.Plugin.Sys/Wox.Plugin.Sys.csproj b/Plugins/Wox.Plugin.Sys/Wox.Plugin.Sys.csproj index 09a1e761f..ffdfa5972 100644 --- a/Plugins/Wox.Plugin.Sys/Wox.Plugin.Sys.csproj +++ b/Plugins/Wox.Plugin.Sys/Wox.Plugin.Sys.csproj @@ -71,9 +71,15 @@ + + PreserveNewest + PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/README.md b/README.md index 2b9da637f..00069024a 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,22 @@ Features - Search for everything—applications, **uwp**, folders, files and more. - Use *pinyin* to search for programs / 支持用 **拼音** 搜索程序 - wyy / wangyiyun → 网易云音乐 -- Keyword plugin search - - search google with `g search_term` +- Keyword plugin search `g search_term` +- Search youtube, google, twitter and many more - Build custom themes at http://www.wox.one/theme/builder - Install plugins from http://www.wox.one/plugin +**New from this fork:** +- Portable mode +- Drastically improved search experience +- Search all subfolders and files +- Option to always run CMD or Powershell as administrator +- Run CMD, Powershell and programs as a different user +- Manage what programs should be loaded +- Highlighting of how results are matched during query search +- Open web search result as a tab or a new window +- Automatic update +- Reload/update plugin data Installation ------------ @@ -42,8 +53,8 @@ Versions marked as **pre-release** are unstable pre-release versions. - Requirements: - .net >= 4.5.2 - - [everything](https://www.voidtools.com/): `.exe` installer + use x64 if your windows is x64 + everything service is running - - [python3](https://www.python.org/downloads/): `.exe` installer + add it to `%PATH%` or set it in WoX settings + - If you want to integrate with [everything](https://www.voidtools.com/): `.exe` installer + use x64 if your windows is x64 + everything service is running. Supported version is 1.3.4.686 + - If you use python plugins, install [python3](https://www.python.org/downloads/): `.exe` installer + add it to `%PATH%` or set it in WoX settings Usage ----- diff --git a/Wox.Core/Plugin/PluginManager.cs b/Wox.Core/Plugin/PluginManager.cs index fc9afb740..0166690b7 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -92,21 +93,36 @@ namespace Wox.Core.Plugin Settings.UpdatePluginSettings(_metadatas); AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); } + + /// + /// Call initialize for all plugins + /// + /// return the list of failed to init plugins or null for none public static void InitializePlugins(IPublicAPI api) { API = api; + var failedPlugins = new ConcurrentQueue(); Parallel.ForEach(AllPlugins, pair => { - var milliseconds = Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", () => + try { - pair.Plugin.Init(new PluginInitContext + var milliseconds = Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", () => { - CurrentPluginMetadata = pair.Metadata, - API = API + pair.Plugin.Init(new PluginInitContext + { + CurrentPluginMetadata = pair.Metadata, + API = API + }); }); - }); - pair.Metadata.InitTime += milliseconds; - Log.Info($"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); + pair.Metadata.InitTime += milliseconds; + Log.Info($"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); + } + catch (Exception e) + { + Log.Exception(nameof(PluginManager), $"Fail to Init plugin: {pair.Metadata.Name}", e); + pair.Metadata.Disabled = true; + failedPlugins.Enqueue(pair); + } }); _contextMenuPlugins = GetPluginsForInterface(); @@ -121,6 +137,11 @@ namespace Wox.Core.Plugin .ForEach(x => NonGlobalPlugins[x] = plugin); } + if (failedPlugins.Any()) + { + var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); + API.ShowMsg($"Fail to Init Plugins", $"Plugins: {failed} - fail to load and would be disabled, please contact plugin creator for help", "", false); + } } public static void InstallPlugin(string path) @@ -128,35 +149,6 @@ namespace Wox.Core.Plugin PluginInstaller.Install(path); } - public static Query QueryInit(string text) //todo is that possible to move it into type Query? - { - // replace multiple white spaces with one white space - var terms = text.Split(new[] { Query.TermSeperater }, StringSplitOptions.RemoveEmptyEntries); - var rawQuery = string.Join(Query.TermSeperater, terms); - var actionKeyword = string.Empty; - var search = rawQuery; - var actionParameters = terms.ToList(); - if (terms.Length == 0) return null; - if (NonGlobalPlugins.ContainsKey(terms[0]) && - !Settings.Plugins[NonGlobalPlugins[terms[0]].Metadata.ID].Disabled) - { - actionKeyword = terms[0]; - actionParameters = terms.Skip(1).ToList(); - search = string.Join(Query.TermSeperater, actionParameters.ToArray()); - } - var query = new Query - { - Terms = terms, - RawQuery = rawQuery, - ActionKeyword = actionKeyword, - Search = search, - // Obsolete value initialisation - ActionName = actionKeyword, - ActionParameters = actionParameters - }; - return query; - } - public static List ValidPluginsForQuery(Query query) { if (NonGlobalPlugins.ContainsKey(query.ActionKeyword)) diff --git a/Wox.Core/Plugin/QueryBuilder.cs b/Wox.Core/Plugin/QueryBuilder.cs new file mode 100644 index 000000000..b01a93291 --- /dev/null +++ b/Wox.Core/Plugin/QueryBuilder.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Wox.Plugin; + +namespace Wox.Core.Plugin +{ + public static class QueryBuilder + { + public static Query Build(string text, Dictionary nonGlobalPlugins) + { + // replace multiple white spaces with one white space + var terms = text.Split(new[] { Query.TermSeperater }, StringSplitOptions.RemoveEmptyEntries); + if (terms.Length == 0) + { // nothing was typed + return null; + } + + var rawQuery = string.Join(Query.TermSeperater, terms); + string actionKeyword, search; + string possibleActionKeyword = terms[0]; + List actionParameters; + if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled) + { // use non global plugin for query + actionKeyword = possibleActionKeyword; + actionParameters = terms.Skip(1).ToList(); + search = actionParameters.Count > 0 ? rawQuery.Substring(actionKeyword.Length + 1) : string.Empty; + } + else + { // non action keyword + actionKeyword = string.Empty; + actionParameters = terms.ToList(); + search = rawQuery; + } + + var query = new Query + { + Terms = terms, + RawQuery = rawQuery, + ActionKeyword = actionKeyword, + Search = search, + // Obsolete value initialisation + ActionName = actionKeyword, + ActionParameters = actionParameters + }; + + return query; + } + } +} \ No newline at end of file diff --git a/Wox.Core/Resource/LocalizationConverter.cs b/Wox.Core/Resource/LocalizationConverter.cs new file mode 100644 index 000000000..d86bf5f7e --- /dev/null +++ b/Wox.Core/Resource/LocalizationConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Windows.Data; + +namespace Wox.Core +{ + public class LocalizationConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (targetType == typeof(string) && value != null) + { + FieldInfo fi = value.GetType().GetField(value.ToString()); + if (fi != null) + { + string localizedDescription = string.Empty; + var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + if ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) + { + localizedDescription = attributes[0].Description; + } + + return (!String.IsNullOrEmpty(localizedDescription)) ? localizedDescription : value.ToString(); + } + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Wox.Core/Resource/LocalizedDescriptionAttribute.cs b/Wox.Core/Resource/LocalizedDescriptionAttribute.cs new file mode 100644 index 000000000..ef1257220 --- /dev/null +++ b/Wox.Core/Resource/LocalizedDescriptionAttribute.cs @@ -0,0 +1,27 @@ +using System.ComponentModel; +using Wox.Core.Resource; + +namespace Wox.Core +{ + public class LocalizedDescriptionAttribute : DescriptionAttribute + { + private readonly Internationalization _translator; + private readonly string _resourceKey; + + public LocalizedDescriptionAttribute(string resourceKey) + { + _translator = InternationalizationManager.Instance; + _resourceKey = resourceKey; + } + + public override string Description + { + get + { + string description = _translator.GetTranslation(_resourceKey); + return string.IsNullOrWhiteSpace(description) ? + string.Format("[[{0}]]", _resourceKey) : description; + } + } + } +} diff --git a/Wox.Core/Updater.cs b/Wox.Core/Updater.cs index 971995f5e..9262d78f4 100644 --- a/Wox.Core/Updater.cs +++ b/Wox.Core/Updater.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.Http; @@ -10,9 +10,11 @@ using JetBrains.Annotations; using Squirrel; using Newtonsoft.Json; using Wox.Core.Resource; +using Wox.Plugin.SharedCommands; using Wox.Infrastructure; using Wox.Infrastructure.Http; using Wox.Infrastructure.Logger; +using System.IO; namespace Wox.Core { @@ -25,14 +27,14 @@ namespace Wox.Core GitHubRepository = gitHubRepository; } - public async Task UpdateApp() + public async Task UpdateApp(bool silentIfLatestVersion = true) { - UpdateManager m; - UpdateInfo u; + UpdateManager updateManager; + UpdateInfo newUpdateInfo; try { - m = await GitHubUpdateManager(GitHubRepository); + updateManager = await GitHubUpdateManager(GitHubRepository); } catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { @@ -43,42 +45,61 @@ namespace Wox.Core try { // UpdateApp CheckForUpdate will return value only if the app is squirrel installed - u = await m.CheckForUpdate().NonNull(); + newUpdateInfo = await updateManager.CheckForUpdate().NonNull(); } catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to api.github.com.", e); - m.Dispose(); + updateManager.Dispose(); return; } - var fr = u.FutureReleaseEntry; - var cr = u.CurrentlyInstalledVersion; - Log.Info($"|Updater.UpdateApp|Future Release <{fr.Formatted()}>"); - if (fr.Version > cr.Version) + var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString()); + var currentVersion = Version.Parse(Constant.Version); + + Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>"); + + if (newReleaseVersion <= currentVersion) { - try - { - await m.DownloadReleases(u.ReleasesToApply); - } - catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) - { - Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); - m.Dispose(); - return; - } - - await m.ApplyReleases(u); - await m.CreateUninstallerRegistryEntry(); - - var newVersionTips = this.NewVersinoTips(fr.Version.ToString()); - - MessageBox.Show(newVersionTips); - Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}"); + if (!silentIfLatestVersion) + MessageBox.Show("You already have the latest Wox version"); + updateManager.Dispose(); + return; } + try + { + await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply); + } + catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) + { + Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); + updateManager.Dispose(); + return; + } + + await updateManager.ApplyReleases(newUpdateInfo); + + if (Constant.IsPortableMode) + { + var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{Constant.PortableFolderName}"; + FilesFolders.Copy(Constant.PortableDataPath, targetDestination); + if (!FilesFolders.VerifyBothFolderFilesEqual(Constant.PortableDataPath, targetDestination)) + MessageBox.Show(string.Format("Wox was not able to move your user profile data to the new update version. Please manually" + + "move your profile data folder from {0} to {1}", Constant.PortableDataPath, targetDestination)); + } + else + { + await updateManager.CreateUninstallerRegistryEntry(); + } + + var newVersionTips = NewVersinoTips(newReleaseVersion.ToString()); + + MessageBox.Show(newVersionTips); + Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}"); + // always dispose UpdateManager - m.Dispose(); + updateManager.Dispose(); } [UsedImplicitly] diff --git a/Wox.Core/Wox.Core.csproj b/Wox.Core/Wox.Core.csproj index b3d0adc8b..cd95ed13e 100644 --- a/Wox.Core/Wox.Core.csproj +++ b/Wox.Core/Wox.Core.csproj @@ -55,6 +55,9 @@ + + + diff --git a/Wox.Infrastructure/Logger/Log.cs b/Wox.Infrastructure/Logger/Log.cs index 10e8128c5..349920076 100644 --- a/Wox.Infrastructure/Logger/Log.cs +++ b/Wox.Infrastructure/Logger/Log.cs @@ -11,7 +11,7 @@ namespace Wox.Infrastructure.Logger { public const string DirectoryName = "Logs"; - public static string CurrentLogDirectory { get; private set; } + public static string CurrentLogDirectory { get; } static Log() { @@ -53,6 +53,14 @@ namespace Wox.Infrastructure.Logger [MethodImpl(MethodImplOptions.Synchronized)] public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "") + { + var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName); + + ExceptionInternal(classNameWithMethod, message, exception); + } + + private static string CheckClassAndMessageAndReturnFullClassWithMethod(string className, string message, + string methodName) { if (string.IsNullOrWhiteSpace(className)) { @@ -60,16 +68,17 @@ namespace Wox.Infrastructure.Logger } if (string.IsNullOrWhiteSpace(message)) - { // todo: not sure we really need that + { + // todo: not sure we really need that LogFaultyFormat($"Fail to specify a message during logging"); } if (!string.IsNullOrWhiteSpace(methodName)) { - className += "." + methodName; + return className + "." + methodName; } - ExceptionInternal(className, message, exception); + return className; } private static void ExceptionInternal(string classAndMethod, string message, System.Exception e) @@ -140,18 +149,48 @@ namespace Wox.Infrastructure.Logger LogInternal(message, LogLevel.Error); } + public static void Error(string className, string message, [CallerMemberName] string methodName = "") + { + LogInternal(LogLevel.Error, className, message, methodName); + } + + private static void LogInternal(LogLevel level, string className, string message, [CallerMemberName] string methodName = "") + { + var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName); + + var logger = LogManager.GetLogger(classNameWithMethod); + + System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}"); + logger.Log(level, message); + } + + public static void Debug(string className, string message, [CallerMemberName] string methodName = "") + { + LogInternal(LogLevel.Debug, className, message, methodName); + } + /// example: "|prefix|unprefixed" public static void Debug(string message) { LogInternal(message, LogLevel.Debug); } + public static void Info(string className, string message, [CallerMemberName] string methodName = "") + { + LogInternal(LogLevel.Info, className, message, methodName); + } + /// example: "|prefix|unprefixed" public static void Info(string message) { LogInternal(message, LogLevel.Info); } + public static void Warn(string className, string message, [CallerMemberName] string methodName = "") + { + LogInternal(LogLevel.Warn, className, message, methodName); + } + /// example: "|prefix|unprefixed" public static void Warn(string message) { diff --git a/Wox.Infrastructure/UI/EnumBindingSource.cs b/Wox.Infrastructure/UI/EnumBindingSource.cs new file mode 100644 index 000000000..c23aacd81 --- /dev/null +++ b/Wox.Infrastructure/UI/EnumBindingSource.cs @@ -0,0 +1,57 @@ +using System; +using System.Windows.Markup; + +namespace Wox.Infrastructure.UI +{ + public class EnumBindingSourceExtension : MarkupExtension + { + private Type _enumType; + public Type EnumType + { + get { return _enumType; } + set + { + if (value != _enumType) + { + if (value != null) + { + Type enumType = Nullable.GetUnderlyingType(value) ?? value; + if (!enumType.IsEnum) + { + throw new ArgumentException("Type must represent an enum."); + } + } + + _enumType = value; + } + } + } + + public EnumBindingSourceExtension() { } + + public EnumBindingSourceExtension(Type enumType) + { + EnumType = enumType; + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + if (_enumType == null) + { + throw new InvalidOperationException("The EnumType must be specified."); + } + + Type actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType; + Array enumValues = Enum.GetValues(actualEnumType); + + if (actualEnumType == _enumType) + { + return enumValues; + } + + Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1); + enumValues.CopyTo(tempArray, 1); + return tempArray; + } + } +} diff --git a/Wox.Infrastructure/UserSettings/PluginSettings.cs b/Wox.Infrastructure/UserSettings/PluginSettings.cs index c47fe7c15..346d98e53 100644 --- a/Wox.Infrastructure/UserSettings/PluginSettings.cs +++ b/Wox.Infrastructure/UserSettings/PluginSettings.cs @@ -28,7 +28,7 @@ namespace Wox.Infrastructure.UserSettings { ID = metadata.ID, Name = metadata.Name, - ActionKeywords = metadata.ActionKeywords, + ActionKeywords = metadata.ActionKeywords, Disabled = metadata.Disabled }; } @@ -39,7 +39,11 @@ namespace Wox.Infrastructure.UserSettings { public string ID { get; set; } public string Name { get; set; } - public List ActionKeywords { get; set; } + public List ActionKeywords { get; set; } // a reference of the action keywords from plugin manager + + /// + /// Used only to save the state of the plugin in settings + /// public bool Disabled { get; set; } } } diff --git a/Wox.Infrastructure/Wox.Infrastructure.csproj b/Wox.Infrastructure/Wox.Infrastructure.csproj index 16194a35a..445454093 100644 --- a/Wox.Infrastructure/Wox.Infrastructure.csproj +++ b/Wox.Infrastructure/Wox.Infrastructure.csproj @@ -72,6 +72,7 @@ + diff --git a/Wox.Infrastructure/Wox.cs b/Wox.Infrastructure/Wox.cs index 35faa102c..d9599e07a 100644 --- a/Wox.Infrastructure/Wox.cs +++ b/Wox.Infrastructure/Wox.cs @@ -16,6 +16,7 @@ namespace Wox.Infrastructure public static readonly string ApplicationDirectory = Directory.GetParent(ProgramDirectory).ToString(); public static readonly string RootDirectory = Directory.GetParent(ApplicationDirectory).ToString(); + public static bool IsPortableMode; public const string PortableFolderName = "UserData"; public static string PortableDataPath = Path.Combine(ProgramDirectory, PortableFolderName); public static string RoamingDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Wox); @@ -23,6 +24,7 @@ namespace Wox.Infrastructure { if (Directory.Exists(PortableDataPath)) { + IsPortableMode = true; return PortableDataPath; } else diff --git a/Wox.Plugin/IPublicAPI.cs b/Wox.Plugin/IPublicAPI.cs index eb3f18fa7..754b76d36 100644 --- a/Wox.Plugin/IPublicAPI.cs +++ b/Wox.Plugin/IPublicAPI.cs @@ -70,13 +70,18 @@ namespace Wox.Plugin /// void ReloadAllPluginData(); + /// + /// Check for new Wox update + /// + void CheckForNewUpdate(); + /// /// Show message box /// /// Message title /// Message subtitle /// Message icon path (relative path to your plugin folder) - void ShowMsg(string title, string subTitle = "", string iconPath = ""); + void ShowMsg(string title, string subTitle = "", string iconPath = "", bool useMainWindowAsOwner = true); /// /// Open setting dialog diff --git a/Wox.Plugin/PluginMetadata.cs b/Wox.Plugin/PluginMetadata.cs index 5b4feb88e..64b8b032c 100644 --- a/Wox.Plugin/PluginMetadata.cs +++ b/Wox.Plugin/PluginMetadata.cs @@ -46,6 +46,9 @@ namespace Wox.Plugin [Obsolete("Use IcoPath")] public string FullIcoPath => IcoPath; + /// + /// Init time include both plugin load time and init time + /// [JsonIgnore] public long InitTime { get; set; } [JsonIgnore] diff --git a/Wox.Plugin/SharedCommands/FilesFolders.cs b/Wox.Plugin/SharedCommands/FilesFolders.cs index 767f00bb8..584a83bc6 100644 --- a/Wox.Plugin/SharedCommands/FilesFolders.cs +++ b/Wox.Plugin/SharedCommands/FilesFolders.cs @@ -42,7 +42,7 @@ namespace Wox.Plugin.SharedCommands Copy(subdir.FullName, temppath); } } - catch(Exception e) + catch (Exception e) { #if DEBUG throw e; @@ -69,7 +69,7 @@ namespace Wox.Plugin.SharedCommands return true; } - catch(Exception e) + catch (Exception e) { #if DEBUG throw e; @@ -88,7 +88,7 @@ namespace Wox.Plugin.SharedCommands if (Directory.Exists(path)) Directory.Delete(path, true); } - catch(Exception e) + catch (Exception e) { #if DEBUG throw e; diff --git a/Wox.Test/QueryBuilderTest.cs b/Wox.Test/QueryBuilderTest.cs new file mode 100644 index 000000000..0402d2ae8 --- /dev/null +++ b/Wox.Test/QueryBuilderTest.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using NUnit.Framework; +using Wox.Core.Plugin; +using Wox.Plugin; + +namespace Wox.Test +{ + public class QueryBuilderTest + { + [Test] + public void ExclusivePluginQueryTest() + { + var nonGlobalPlugins = new Dictionary + { + {">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List {">"}}}} + }; + + Query q = QueryBuilder.Build("> file.txt file2 file3", nonGlobalPlugins); + + Assert.AreEqual("file.txt file2 file3", q.Search); + Assert.AreEqual(">", q.ActionKeyword); + } + + [Test] + public void ExclusivePluginQueryIgnoreDisabledTest() + { + var nonGlobalPlugins = new Dictionary + { + {">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List {">"}, Disabled = true}}} + }; + + Query q = QueryBuilder.Build("> file.txt file2 file3", nonGlobalPlugins); + + Assert.AreEqual("> file.txt file2 file3", q.Search); + } + + [Test] + public void GenericPluginQueryTest() + { + Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary()); + + Assert.AreEqual("file.txt file2 file3", q.Search); + Assert.AreEqual("", q.ActionKeyword); + + Assert.AreEqual("file.txt", q.FirstSearch); + Assert.AreEqual("file2", q.SecondSearch); + Assert.AreEqual("file3", q.ThirdSearch); + Assert.AreEqual("file2 file3", q.SecondToEndSearch); + } + } +} diff --git a/Wox.Test/QueryTest.cs b/Wox.Test/QueryTest.cs deleted file mode 100644 index b7a0917ef..000000000 --- a/Wox.Test/QueryTest.cs +++ /dev/null @@ -1,33 +0,0 @@ -using NUnit.Framework; -using Wox.Core.Plugin; -using Wox.Plugin; - -namespace Wox.Test -{ - public class QueryTest - { - [Test] - [Ignore("Current query is tightly integrated with GUI, can't be tested.")] - public void ExclusivePluginQueryTest() - { - Query q = PluginManager.QueryInit("> file.txt file2 file3"); - - Assert.AreEqual(q.FirstSearch, "file.txt"); - Assert.AreEqual(q.SecondSearch, "file2"); - Assert.AreEqual(q.ThirdSearch, "file3"); - Assert.AreEqual(q.SecondToEndSearch, "file2 file3"); - } - - [Test] - [Ignore("Current query is tightly integrated with GUI, can't be tested.")] - public void GenericPluginQueryTest() - { - Query q = PluginManager.QueryInit("file.txt file2 file3"); - - Assert.AreEqual(q.FirstSearch, "file.txt"); - Assert.AreEqual(q.SecondSearch, "file2"); - Assert.AreEqual(q.ThirdSearch, "file3"); - Assert.AreEqual(q.SecondToEndSearch, "file2 file3"); - } - } -} diff --git a/Wox.Test/Wox.Test.csproj b/Wox.Test/Wox.Test.csproj index 7a3ef1f0c..2b1e6a78a 100644 --- a/Wox.Test/Wox.Test.csproj +++ b/Wox.Test/Wox.Test.csproj @@ -45,7 +45,7 @@ - + diff --git a/Wox/PublicAPIInstance.cs b/Wox/PublicAPIInstance.cs index ab198d6bf..58915f87b 100644 --- a/Wox/PublicAPIInstance.cs +++ b/Wox/PublicAPIInstance.cs @@ -5,6 +5,7 @@ using System.Net; using System.Threading.Tasks; using System.Windows; using Squirrel; +using Wox.Core; using Wox.Core.Plugin; using Wox.Core.Resource; using Wox.Helper; @@ -65,6 +66,11 @@ namespace Wox UpdateManager.RestartApp(); } + public void CheckForNewUpdate() + { + _settingsVM.UpdateApp(); + } + public void SaveAppAllSettings() { _mainVM.Save(); @@ -91,12 +97,12 @@ namespace Wox _mainVM.MainWindowVisibility = Visibility.Visible; } - public void ShowMsg(string title, string subTitle = "", string iconPath = "") + public void ShowMsg(string title, string subTitle = "", string iconPath = "", bool useMainWindowAsOwner = true) { Application.Current.Dispatcher.Invoke(() => { - var m = new Msg { Owner = Application.Current.MainWindow }; - m.Show(title, subTitle, iconPath); + var msg = useMainWindowAsOwner ? new Msg {Owner = Application.Current.MainWindow} : new Msg(); + msg.Show(title, subTitle, iconPath); }); } diff --git a/Wox/SettingWindow.xaml b/Wox/SettingWindow.xaml index a6f23814f..1eddd8ab6 100644 --- a/Wox/SettingWindow.xaml +++ b/Wox/SettingWindow.xaml @@ -33,7 +33,8 @@ - + @@ -51,8 +52,7 @@ - + diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index 67d67351e..52addf362 100644 --- a/Wox/SettingWindow.xaml.cs +++ b/Wox/SettingWindow.xaml.cs @@ -215,7 +215,8 @@ namespace Wox private void OnPluginToggled(object sender, RoutedEventArgs e) { var id = _viewModel.SelectedPlugin.PluginPair.Metadata.ID; - _settings.PluginSettings.Plugins[id].Disabled = _viewModel.SelectedPlugin.PluginPair.Metadata.Disabled; + // used to sync the current status from the plugin manager into the setting to keep consistency after save + _settings.PluginSettings.Plugins[id].Disabled = _viewModel.SelectedPlugin.PluginPair.Metadata.Disabled; } private void OnPluginActionKeywordsClick(object sender, MouseButtonEventArgs e) diff --git a/Wox/ViewModel/MainViewModel.cs b/Wox/ViewModel/MainViewModel.cs index bd1314a27..acdd7c040 100644 --- a/Wox/ViewModel/MainViewModel.cs +++ b/Wox/ViewModel/MainViewModel.cs @@ -377,7 +377,7 @@ namespace Wox.ViewModel ProgressBarVisibility = Visibility.Hidden; _isQueryRunning = true; - var query = PluginManager.QueryInit(QueryText.Trim()); + var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); if (query != null) { // handle the exclusiveness of plugin using action keyword @@ -401,8 +401,7 @@ namespace Wox.ViewModel { Parallel.ForEach(plugins, parallelOptions, plugin => { - var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; - if (!config.Disabled) + if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); diff --git a/Wox/ViewModel/SettingWindowViewModel.cs b/Wox/ViewModel/SettingWindowViewModel.cs index d26f86cb9..0a240aec7 100644 --- a/Wox/ViewModel/SettingWindowViewModel.cs +++ b/Wox/ViewModel/SettingWindowViewModel.cs @@ -47,7 +47,7 @@ namespace Wox.ViewModel public async void UpdateApp() { - await _updater.UpdateApp(); + await _updater.UpdateApp(false); } public void EnablePortableMode() @@ -163,26 +163,11 @@ namespace Wox.ViewModel { get { - var plugins = PluginManager.AllPlugins; - var settings = Settings.PluginSettings.Plugins; - plugins.Sort((a, b) => - { - var d1 = settings[a.Metadata.ID].Disabled; - var d2 = settings[b.Metadata.ID].Disabled; - if (d1 == d2) - { - return string.Compare(a.Metadata.Name, b.Metadata.Name, StringComparison.CurrentCulture); - } - else - { - return d1.CompareTo(d2); - } - }); - - var metadatas = plugins.Select(p => new PluginViewModel - { - PluginPair = p, - }).ToList(); + var metadatas = PluginManager.AllPlugins + .OrderBy(x => x.Metadata.Disabled) + .ThenBy(y => y.Metadata.Name) + .Select(p => new PluginViewModel { PluginPair = p}) + .ToList(); return metadatas; } }