From ba1e22955e101c247b8d6be5aa92eacf7588958b Mon Sep 17 00:00:00 2001 From: bao-qian Date: Thu, 5 May 2016 16:08:44 +0100 Subject: [PATCH] Web search suggestion is loaded async 1. suggestion is async 2. if ping time of domain less than 300ms, then suggestion is still sync 3. #578 #539 --- .../SuggestionSources/Baidu.cs | 4 +- .../SuggestionSources/Google.cs | 3 +- .../SuggestionSources/ISuggestionSource.cs | 25 +- .../SuggestionSourceFactory.cs | 20 -- .../Wox.Plugin.WebSearch/WebSearchPlugin.cs | 97 ++++-- .../Wox.Plugin.WebSearch.csproj | 1 - Wox.Core/Plugin/PluginManager.cs | 49 +-- Wox.Plugin/Feature.cs | 15 +- Wox.Plugin/Result.cs | 1 + Wox/App.xaml.cs | 1 - Wox/ViewModel/MainViewModel.cs | 294 +++++++++--------- 11 files changed, 293 insertions(+), 217 deletions(-) delete mode 100644 Plugins/Wox.Plugin.WebSearch/SuggestionSources/SuggestionSourceFactory.cs diff --git a/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Baidu.cs index 149374fb23..c4a472ac97 100644 --- a/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -8,8 +8,10 @@ using Wox.Infrastructure.Http; namespace Wox.Plugin.WebSearch.SuggestionSources { - public class Baidu : AbstractSuggestionSource + public class Baidu : SuggestionSource { + public override string Domain { get; set; } = "www.baidu.com"; + Regex reg = new Regex("window.baidu.sug\\((.*)\\)"); public override List GetSuggestions(string query) diff --git a/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Google.cs index df572a98bc..fd732375f5 100644 --- a/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Google.cs @@ -7,8 +7,9 @@ using Wox.Infrastructure.Http; namespace Wox.Plugin.WebSearch.SuggestionSources { - public class Google : AbstractSuggestionSource + public class Google : SuggestionSource { + public override string Domain { get; set; } = "www.google.com"; public override List GetSuggestions(string query) { var result = HttpRequest.Get("https://www.google.com/complete/search?output=chrome&q=" + Uri.EscapeUriString(query), Proxy); diff --git a/Plugins/Wox.Plugin.WebSearch/SuggestionSources/ISuggestionSource.cs b/Plugins/Wox.Plugin.WebSearch/SuggestionSources/ISuggestionSource.cs index 064a939f9d..75556602ee 100644 --- a/Plugins/Wox.Plugin.WebSearch/SuggestionSources/ISuggestionSource.cs +++ b/Plugins/Wox.Plugin.WebSearch/SuggestionSources/ISuggestionSource.cs @@ -2,20 +2,31 @@ namespace Wox.Plugin.WebSearch.SuggestionSources { - public interface ISuggestionSource - { - List GetSuggestions(string query); - } - - public abstract class AbstractSuggestionSource : ISuggestionSource + public abstract class SuggestionSource { + public virtual string Domain { get; set; } public IHttpProxy Proxy { get; set; } - public AbstractSuggestionSource(IHttpProxy httpProxy) + public SuggestionSource(IHttpProxy httpProxy) { Proxy = httpProxy; } public abstract List GetSuggestions(string query); + + public static SuggestionSource GetSuggestionSource(string name, PluginInitContext context) + { + switch (name.ToLower()) + { + case "google": + return new Google(context.Proxy); + + case "baidu": + return new Baidu(context.Proxy); + + default: + return null; + } + } } } diff --git a/Plugins/Wox.Plugin.WebSearch/SuggestionSources/SuggestionSourceFactory.cs b/Plugins/Wox.Plugin.WebSearch/SuggestionSources/SuggestionSourceFactory.cs deleted file mode 100644 index 203c5c337e..0000000000 --- a/Plugins/Wox.Plugin.WebSearch/SuggestionSources/SuggestionSourceFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Wox.Plugin.WebSearch.SuggestionSources -{ - public class SuggestionSourceFactory - { - public static ISuggestionSource GetSuggestionSource(string name,PluginInitContext context) - { - switch (name.ToLower()) - { - case "google": - return new Google(context.Proxy); - - case "baidu": - return new Baidu(context.Proxy); - - default: - return null; - } - } - } -} diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs b/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs index 8232c7f28e..12796c64db 100644 --- a/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs +++ b/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs @@ -3,19 +3,26 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Net.NetworkInformation; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Controls; +using System.Windows.Documents; using JetBrains.Annotations; using Wox.Infrastructure.Storage; using Wox.Plugin.WebSearch.SuggestionSources; namespace Wox.Plugin.WebSearch { - public class WebSearchPlugin : IPlugin, ISettingProvider, IPluginI18n, IMultipleActionKeywords, ISavable + public class WebSearchPlugin : IPlugin, ISettingProvider, IPluginI18n, IMultipleActionKeywords, ISavable, IResultUpdated { public PluginInitContext Context { get; private set; } private PluginJsonStorage _storage; private Settings _settings; + private CancellationTokenSource _updateSource; + private CancellationToken _updateToken; + public const string ImageDirectory = "Images"; public static string PluginDirectory; @@ -26,7 +33,10 @@ namespace Wox.Plugin.WebSearch public List Query(Query query) { - List results = new List(); + _updateSource?.Cancel(); + _updateSource = new CancellationTokenSource(); + _updateToken = _updateSource.Token; + WebSearch webSearch = _settings.WebSearches.FirstOrDefault(o => o.ActionKeyword == query.ActionKeyword && o.Enabled); @@ -37,36 +47,74 @@ namespace Wox.Plugin.WebSearch string subtitle = Context.API.GetTranslation("wox_plugin_websearch_search") + " " + webSearch.Title; if (string.IsNullOrEmpty(keyword)) { - title = subtitle; - subtitle = string.Empty; - } - var result = new Result - { - Title = title, - SubTitle = subtitle, - Score = 6, - IcoPath = webSearch.IconPath, - Action = c => + var result = new Result { - Process.Start(webSearch.Url.Replace("{q}", Uri.EscapeDataString(keyword ?? string.Empty))); - return true; - } - }; - results.Add(result); - - if (_settings.EnableWebSearchSuggestion && !string.IsNullOrEmpty(keyword)) + Title = subtitle, + SubTitle = string.Empty, + IcoPath = webSearch.IconPath + }; + return new List { result }; + } + else { - // todo use Task.Wait when .net upgraded - results.AddRange(ResultsFromSuggestions(keyword, subtitle, webSearch)); + var results = new List(); + var result = new Result + { + Title = title, + SubTitle = subtitle, + Score = 6, + IcoPath = webSearch.IconPath, + Action = c => + { + Process.Start(webSearch.Url.Replace("{q}", Uri.EscapeDataString(keyword))); + return true; + } + }; + results.Add(result); + UpdateResultsFromSuggestion(results, keyword, subtitle, webSearch, query); + return results; + } + } + else + { + return new List(); + } + } + + private void UpdateResultsFromSuggestion(List results, string keyword, string subtitle, WebSearch webSearch, Query query) + { + if (_settings.EnableWebSearchSuggestion) + { + var waittime = 300; + var fastDomain = Task.Factory.StartNew(() => + { + var ping = new Ping(); + var source = SuggestionSource.GetSuggestionSource(_settings.WebSearchSuggestionSource, Context); + ping.Send(source.Domain); + }, _updateToken).Wait(waittime); + if (fastDomain) + { + results.AddRange(ResultsFromSuggestions(keyword, subtitle, webSearch)); + } + else + { + Task.Factory.StartNew(() => + { + results.AddRange(ResultsFromSuggestions(keyword, subtitle, webSearch)); + ResultsUpdated?.Invoke(this, new ResultUpdatedEventHandlerArgs + { + Results = results, + Query = query + }); + }, _updateToken); } } - return results; } private IEnumerable ResultsFromSuggestions(string keyword, string subtitle, WebSearch webSearch) { - ISuggestionSource sugg = SuggestionSourceFactory.GetSuggestionSource(_settings.WebSearchSuggestionSource, Context); - var suggestions = sugg?.GetSuggestions(keyword); + var source = SuggestionSource.GetSuggestionSource(_settings.WebSearchSuggestionSource, Context); + var suggestions = source?.GetSuggestions(keyword); if (suggestions != null) { var resultsFromSuggestion = suggestions.Select(o => new Result @@ -135,5 +183,6 @@ namespace Wox.Plugin.WebSearch } public event ActionKeywordsChangedEventHandler ActionKeywordsChanged; + public event ResultUpdatedEventHandler ResultsUpdated; } } diff --git a/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj b/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj index 3d52d10179..7207fc8f76 100644 --- a/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj +++ b/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj @@ -61,7 +61,6 @@ - WebSearchesSetting.xaml diff --git a/Wox.Core/Plugin/PluginManager.cs b/Wox.Core/Plugin/PluginManager.cs index 7f32819dce..3a53b8c710 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -30,7 +30,7 @@ namespace Wox.Core.Plugin public static IPublicAPI API { private set; get; } private static PluginsSettings _settings; - + private static List _metadatas; private static readonly string[] Directories = {Infrastructure.Wox.PreinstalledDirectory, Infrastructure.Wox.UserDirectory }; private static void ValidateUserDirectory() @@ -41,11 +41,6 @@ namespace Wox.Core.Plugin } } - static PluginManager() - { - ValidateUserDirectory(); - } - public static void Save() { foreach (var plugin in AllPlugins) @@ -55,14 +50,19 @@ namespace Wox.Core.Plugin } } + static PluginManager() + { + ValidateUserDirectory(); + + // todo happlebao temp hack to let MainVM to register ResultsUpdated event + _metadatas = PluginConfig.Parse(Directories); + AllPlugins = PluginsLoader.CSharpPlugins(_metadatas).ToList(); + } public static void InitializePlugins(IPublicAPI api, PluginsSettings settings) { _settings = settings; - - var metadatas = PluginConfig.Parse(Directories); - var plugins1 = PluginsLoader.CSharpPlugins(metadatas); - var plugins2 = PluginsLoader.PythonPlugins(metadatas, _settings.PythonDirectory); - AllPlugins = plugins1.Concat(plugins2).ToList(); + var plugins = PluginsLoader.PythonPlugins(_metadatas, _settings.PythonDirectory); + AllPlugins = AllPlugins.Concat(plugins).ToList(); _settings.UpdatePluginSettings(AllPlugins); //load plugin i18n languages @@ -106,6 +106,7 @@ namespace Wox.Core.Plugin } } }); + } public static void InstallPlugin(string path) @@ -193,16 +194,12 @@ namespace Wox.Core.Plugin var results = new List(); try { - var milliseconds = Stopwatch.Normal($"Plugin.Query cost for {pair.Metadata.Name}", () => - { - results = pair.Plugin.Query(query) ?? results; - results.ForEach(o => - { - o.PluginDirectory = pair.Metadata.PluginDirectory; - o.PluginID = pair.Metadata.ID; - o.OriginQuery = query; - }); - }); + var metadata = pair.Metadata; + var milliseconds = Stopwatch.Normal($"Plugin.Query cost for {metadata.Name}", () => + { + results = pair.Plugin.Query(query) ?? results; + UpdatePluginMetadata(results, metadata, query); + }); pair.QueryCount += 1; pair.AvgQueryTime = pair.QueryCount == 1 ? milliseconds : (pair.AvgQueryTime + milliseconds) / 2; } @@ -213,6 +210,16 @@ namespace Wox.Core.Plugin return results; } + public static void UpdatePluginMetadata(List results, PluginMetadata metadata, Query query) + { + foreach (var r in results) + { + r.PluginDirectory = metadata.PluginDirectory; + r.PluginID = metadata.ID; + r.OriginQuery = query; + } + } + private static bool IsGlobalPlugin(PluginMetadata metadata) { return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign); diff --git a/Wox.Plugin/Feature.cs b/Wox.Plugin/Feature.cs index 472149d4f7..815c9feebe 100644 --- a/Wox.Plugin/Feature.cs +++ b/Wox.Plugin/Feature.cs @@ -37,7 +37,7 @@ namespace Wox.Plugin string GetTranslatedPluginDescription(); } - public interface IMultipleActionKeywords + public interface IMultipleActionKeywords : IFeatures { event ActionKeywordsChangedEventHandler ActionKeywordsChanged; } @@ -49,4 +49,17 @@ namespace Wox.Plugin } public delegate void ActionKeywordsChangedEventHandler(IMultipleActionKeywords sender, ActionKeywordsChangedEventArgs e); + + public interface IResultUpdated : IFeatures + { + event ResultUpdatedEventHandler ResultsUpdated; + } + + public delegate void ResultUpdatedEventHandler(IResultUpdated sender, ResultUpdatedEventHandlerArgs e); + + public class ResultUpdatedEventHandlerArgs + { + public List Results; + public Query Query; + } } diff --git a/Wox.Plugin/Result.cs b/Wox.Plugin/Result.cs index 3f550c9b59..5f6e51421d 100644 --- a/Wox.Plugin/Result.cs +++ b/Wox.Plugin/Result.cs @@ -7,6 +7,7 @@ namespace Wox.Plugin public class Result { + private string _pluginDirectory; private string _icoPath; public string Title { get; set; } diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index fb05f85cb6..0855fd8117 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -46,7 +46,6 @@ namespace Wox API = new PublicAPIInstance(mainVM, mainVM._settings); PluginManager.InitializePlugins(API, pluginsSettings); - Window = new MainWindow(mainVM._settings, mainVM); NotifyIconManager notifyIconManager = new NotifyIconManager(API); CommandArgsFactory.Execute(e.Args.ToList()); diff --git a/Wox/ViewModel/MainViewModel.cs b/Wox/ViewModel/MainViewModel.cs index fec1e9b0a5..f3ea00fc11 100644 --- a/Wox/ViewModel/MainViewModel.cs +++ b/Wox/ViewModel/MainViewModel.cs @@ -79,154 +79,22 @@ namespace Wox.ViewModel InitializeResultListBox(); InitializeContextMenu(); InitializeKeyCommands(); + RegisterResultsUpdatedEvent(); } - public void Save() + private void RegisterResultsUpdatedEvent() { - _settingsStorage.Save(); - _queryHistoryStorage.Save(); - _userSelectedRecordStorage.Save(); - _topMostRecordStorage.Save(); - } - - #endregion - - #region ViewModel Properties - - public ResultsViewModel Results { get; private set; } - - public ResultsViewModel ContextMenu { get; private set; } - - public string QueryText - { - get + foreach (var pair in PluginManager.GetPluginsForInterface()) { - return _queryText; - } - set - { - _queryText = value; - OnPropertyChanged(); - - if (_ignoreTextChange) + var plugin = (IResultUpdated) pair.Plugin; + plugin.ResultsUpdated += (s, e) => { - _ignoreTextChange = false; - } - else - { - HandleQueryTextUpdated(); - } + PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); + UpdateResultView(e.Results, pair.Metadata, e.Query); + }; } } - public double Left - { - get - { - return _left; - } - set - { - _left = value; - OnPropertyChanged(); - } - } - - public double Top - { - get - { - return _top; - } - set - { - _top = value; - OnPropertyChanged(); - } - } - - public Visibility ContextMenuVisibility - - { - get - { - return _contextMenuVisibility; - } - set - { - _contextMenuVisibility = value; - OnPropertyChanged(); - - _ignoreTextChange = true; - if (!value.IsVisible()) - { - QueryText = _queryTextBeforeLoadContextMenu; - ResultListBoxVisibility = Visibility.Visible; - OnCursorMovedToEnd(); - } - else - { - _queryTextBeforeLoadContextMenu = QueryText; - QueryText = ""; - ResultListBoxVisibility = Visibility.Collapsed; - } - } - } - - public Visibility ProgressBarVisibility - { - get - { - return _progressBarVisibility; - } - set - { - _progressBarVisibility = value; - OnPropertyChanged(); - } - } - - public Visibility ResultListBoxVisibility - { - get - { - return _resultListBoxVisibility; - } - set - { - _resultListBoxVisibility = value; - OnPropertyChanged(); - } - } - - public Visibility MainWindowVisibility - { - get - { - return _mainWindowVisibility; - } - set - { - _mainWindowVisibility = value; - OnPropertyChanged(); - MainWindowVisibilityChanged?.Invoke(this, new EventArgs()); - } - } - - public ICommand EscCommand { get; set; } - public ICommand SelectNextItemCommand { get; set; } - public ICommand SelectPrevItemCommand { get; set; } - public ICommand DisplayNextQueryCommand { get; set; } - public ICommand DisplayPrevQueryCommand { get; set; } - public ICommand SelectNextPageCommand { get; set; } - public ICommand SelectPrevPageCommand { get; set; } - public ICommand StartHelpCommand { get; set; } - public ICommand LoadContextMenuCommand { get; set; } - public ICommand OpenResultCommand { get; set; } - public ICommand BackCommand { get; set; } - #endregion - - #region Private Methods private void InitializeKeyCommands() { @@ -389,6 +257,144 @@ namespace Wox.ViewModel } } } + #endregion + + #region ViewModel Properties + + public ResultsViewModel Results { get; private set; } + + public ResultsViewModel ContextMenu { get; private set; } + + public string QueryText + { + get + { + return _queryText; + } + set + { + _queryText = value; + OnPropertyChanged(); + + if (_ignoreTextChange) + { + _ignoreTextChange = false; + } + else + { + HandleQueryTextUpdated(); + } + } + } + + public double Left + { + get + { + return _left; + } + set + { + _left = value; + OnPropertyChanged(); + } + } + + public double Top + { + get + { + return _top; + } + set + { + _top = value; + OnPropertyChanged(); + } + } + + public Visibility ContextMenuVisibility + + { + get + { + return _contextMenuVisibility; + } + set + { + _contextMenuVisibility = value; + OnPropertyChanged(); + + _ignoreTextChange = true; + if (!value.IsVisible()) + { + QueryText = _queryTextBeforeLoadContextMenu; + ResultListBoxVisibility = Visibility.Visible; + OnCursorMovedToEnd(); + } + else + { + _queryTextBeforeLoadContextMenu = QueryText; + QueryText = ""; + ResultListBoxVisibility = Visibility.Collapsed; + } + } + } + + public Visibility ProgressBarVisibility + { + get + { + return _progressBarVisibility; + } + set + { + _progressBarVisibility = value; + OnPropertyChanged(); + } + } + + public Visibility ResultListBoxVisibility + { + get + { + return _resultListBoxVisibility; + } + set + { + _resultListBoxVisibility = value; + OnPropertyChanged(); + } + } + + public Visibility MainWindowVisibility + { + get + { + return _mainWindowVisibility; + } + set + { + _mainWindowVisibility = value; + OnPropertyChanged(); + MainWindowVisibilityChanged?.Invoke(this, new EventArgs()); + } + } + + public ICommand EscCommand { get; set; } + public ICommand SelectNextItemCommand { get; set; } + public ICommand SelectPrevItemCommand { get; set; } + public ICommand DisplayNextQueryCommand { get; set; } + public ICommand DisplayPrevQueryCommand { get; set; } + public ICommand SelectNextPageCommand { get; set; } + public ICommand SelectPrevPageCommand { get; set; } + public ICommand StartHelpCommand { get; set; } + public ICommand LoadContextMenuCommand { get; set; } + public ICommand OpenResultCommand { get; set; } + public ICommand BackCommand { get; set; } + #endregion + + #region Private Methods private void QueryContextMenu() { @@ -569,6 +575,14 @@ namespace Wox.ViewModel #region Public Methods + public void Save() + { + _settingsStorage.Save(); + _queryHistoryStorage.Save(); + _userSelectedRecordStorage.Save(); + _topMostRecordStorage.Save(); + } + public void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) { _queryHasReturn = true;