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
This commit is contained in:
bao-qian 2016-05-05 16:08:44 +01:00
parent c41847c0d7
commit ba1e22955e
11 changed files with 293 additions and 217 deletions

View file

@ -8,8 +8,10 @@ using Wox.Infrastructure.Http;
namespace Wox.Plugin.WebSearch.SuggestionSources 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\\((.*)\\)"); Regex reg = new Regex("window.baidu.sug\\((.*)\\)");
public override List<string> GetSuggestions(string query) public override List<string> GetSuggestions(string query)

View file

@ -7,8 +7,9 @@ using Wox.Infrastructure.Http;
namespace Wox.Plugin.WebSearch.SuggestionSources 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<string> GetSuggestions(string query) public override List<string> GetSuggestions(string query)
{ {
var result = HttpRequest.Get("https://www.google.com/complete/search?output=chrome&q=" + Uri.EscapeUriString(query), Proxy); var result = HttpRequest.Get("https://www.google.com/complete/search?output=chrome&q=" + Uri.EscapeUriString(query), Proxy);

View file

@ -2,20 +2,31 @@
namespace Wox.Plugin.WebSearch.SuggestionSources namespace Wox.Plugin.WebSearch.SuggestionSources
{ {
public interface ISuggestionSource public abstract class SuggestionSource
{
List<string> GetSuggestions(string query);
}
public abstract class AbstractSuggestionSource : ISuggestionSource
{ {
public virtual string Domain { get; set; }
public IHttpProxy Proxy { get; set; } public IHttpProxy Proxy { get; set; }
public AbstractSuggestionSource(IHttpProxy httpProxy) public SuggestionSource(IHttpProxy httpProxy)
{ {
Proxy = httpProxy; Proxy = httpProxy;
} }
public abstract List<string> GetSuggestions(string query); public abstract List<string> 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;
}
}
} }
} }

View file

@ -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;
}
}
}
}

View file

@ -3,19 +3,26 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents;
using JetBrains.Annotations; using JetBrains.Annotations;
using Wox.Infrastructure.Storage; using Wox.Infrastructure.Storage;
using Wox.Plugin.WebSearch.SuggestionSources; using Wox.Plugin.WebSearch.SuggestionSources;
namespace Wox.Plugin.WebSearch 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; } public PluginInitContext Context { get; private set; }
private PluginJsonStorage<Settings> _storage; private PluginJsonStorage<Settings> _storage;
private Settings _settings; private Settings _settings;
private CancellationTokenSource _updateSource;
private CancellationToken _updateToken;
public const string ImageDirectory = "Images"; public const string ImageDirectory = "Images";
public static string PluginDirectory; public static string PluginDirectory;
@ -26,7 +33,10 @@ namespace Wox.Plugin.WebSearch
public List<Result> Query(Query query) public List<Result> Query(Query query)
{ {
List<Result> results = new List<Result>(); _updateSource?.Cancel();
_updateSource = new CancellationTokenSource();
_updateToken = _updateSource.Token;
WebSearch webSearch = WebSearch webSearch =
_settings.WebSearches.FirstOrDefault(o => o.ActionKeyword == query.ActionKeyword && o.Enabled); _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; string subtitle = Context.API.GetTranslation("wox_plugin_websearch_search") + " " + webSearch.Title;
if (string.IsNullOrEmpty(keyword)) if (string.IsNullOrEmpty(keyword))
{ {
title = subtitle; var result = new Result
subtitle = string.Empty;
}
var result = new Result
{
Title = title,
SubTitle = subtitle,
Score = 6,
IcoPath = webSearch.IconPath,
Action = c =>
{ {
Process.Start(webSearch.Url.Replace("{q}", Uri.EscapeDataString(keyword ?? string.Empty))); Title = subtitle,
return true; SubTitle = string.Empty,
} IcoPath = webSearch.IconPath
}; };
results.Add(result); return new List<Result> { result };
}
if (_settings.EnableWebSearchSuggestion && !string.IsNullOrEmpty(keyword)) else
{ {
// todo use Task.Wait when .net upgraded var results = new List<Result>();
results.AddRange(ResultsFromSuggestions(keyword, subtitle, webSearch)); 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<Result>();
}
}
private void UpdateResultsFromSuggestion(List<Result> 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<Result> ResultsFromSuggestions(string keyword, string subtitle, WebSearch webSearch) private IEnumerable<Result> ResultsFromSuggestions(string keyword, string subtitle, WebSearch webSearch)
{ {
ISuggestionSource sugg = SuggestionSourceFactory.GetSuggestionSource(_settings.WebSearchSuggestionSource, Context); var source = SuggestionSource.GetSuggestionSource(_settings.WebSearchSuggestionSource, Context);
var suggestions = sugg?.GetSuggestions(keyword); var suggestions = source?.GetSuggestions(keyword);
if (suggestions != null) if (suggestions != null)
{ {
var resultsFromSuggestion = suggestions.Select(o => new Result var resultsFromSuggestion = suggestions.Select(o => new Result
@ -135,5 +183,6 @@ namespace Wox.Plugin.WebSearch
} }
public event ActionKeywordsChangedEventHandler ActionKeywordsChanged; public event ActionKeywordsChangedEventHandler ActionKeywordsChanged;
public event ResultUpdatedEventHandler ResultsUpdated;
} }
} }

View file

@ -61,7 +61,6 @@
<Compile Include="SuggestionSources\Baidu.cs" /> <Compile Include="SuggestionSources\Baidu.cs" />
<Compile Include="SuggestionSources\Google.cs" /> <Compile Include="SuggestionSources\Google.cs" />
<Compile Include="SuggestionSources\ISuggestionSource.cs" /> <Compile Include="SuggestionSources\ISuggestionSource.cs" />
<Compile Include="SuggestionSources\SuggestionSourceFactory.cs" />
<Compile Include="WebSearch.cs" /> <Compile Include="WebSearch.cs" />
<Compile Include="WebSearchesSetting.xaml.cs"> <Compile Include="WebSearchesSetting.xaml.cs">
<DependentUpon>WebSearchesSetting.xaml</DependentUpon> <DependentUpon>WebSearchesSetting.xaml</DependentUpon>

View file

@ -30,7 +30,7 @@ namespace Wox.Core.Plugin
public static IPublicAPI API { private set; get; } public static IPublicAPI API { private set; get; }
private static PluginsSettings _settings; private static PluginsSettings _settings;
private static List<PluginMetadata> _metadatas;
private static readonly string[] Directories = {Infrastructure.Wox.PreinstalledDirectory, Infrastructure.Wox.UserDirectory }; private static readonly string[] Directories = {Infrastructure.Wox.PreinstalledDirectory, Infrastructure.Wox.UserDirectory };
private static void ValidateUserDirectory() private static void ValidateUserDirectory()
@ -41,11 +41,6 @@ namespace Wox.Core.Plugin
} }
} }
static PluginManager()
{
ValidateUserDirectory();
}
public static void Save() public static void Save()
{ {
foreach (var plugin in AllPlugins) 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) public static void InitializePlugins(IPublicAPI api, PluginsSettings settings)
{ {
_settings = settings; _settings = settings;
var plugins = PluginsLoader.PythonPlugins(_metadatas, _settings.PythonDirectory);
var metadatas = PluginConfig.Parse(Directories); AllPlugins = AllPlugins.Concat(plugins).ToList();
var plugins1 = PluginsLoader.CSharpPlugins(metadatas);
var plugins2 = PluginsLoader.PythonPlugins(metadatas, _settings.PythonDirectory);
AllPlugins = plugins1.Concat(plugins2).ToList();
_settings.UpdatePluginSettings(AllPlugins); _settings.UpdatePluginSettings(AllPlugins);
//load plugin i18n languages //load plugin i18n languages
@ -106,6 +106,7 @@ namespace Wox.Core.Plugin
} }
} }
}); });
} }
public static void InstallPlugin(string path) public static void InstallPlugin(string path)
@ -193,16 +194,12 @@ namespace Wox.Core.Plugin
var results = new List<Result>(); var results = new List<Result>();
try try
{ {
var milliseconds = Stopwatch.Normal($"Plugin.Query cost for {pair.Metadata.Name}", () => var metadata = pair.Metadata;
{ var milliseconds = Stopwatch.Normal($"Plugin.Query cost for {metadata.Name}", () =>
results = pair.Plugin.Query(query) ?? results; {
results.ForEach(o => results = pair.Plugin.Query(query) ?? results;
{ UpdatePluginMetadata(results, metadata, query);
o.PluginDirectory = pair.Metadata.PluginDirectory; });
o.PluginID = pair.Metadata.ID;
o.OriginQuery = query;
});
});
pair.QueryCount += 1; pair.QueryCount += 1;
pair.AvgQueryTime = pair.QueryCount == 1 ? milliseconds : (pair.AvgQueryTime + milliseconds) / 2; pair.AvgQueryTime = pair.QueryCount == 1 ? milliseconds : (pair.AvgQueryTime + milliseconds) / 2;
} }
@ -213,6 +210,16 @@ namespace Wox.Core.Plugin
return results; return results;
} }
public static void UpdatePluginMetadata(List<Result> 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) private static bool IsGlobalPlugin(PluginMetadata metadata)
{ {
return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign); return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign);

View file

@ -37,7 +37,7 @@ namespace Wox.Plugin
string GetTranslatedPluginDescription(); string GetTranslatedPluginDescription();
} }
public interface IMultipleActionKeywords public interface IMultipleActionKeywords : IFeatures
{ {
event ActionKeywordsChangedEventHandler ActionKeywordsChanged; event ActionKeywordsChangedEventHandler ActionKeywordsChanged;
} }
@ -49,4 +49,17 @@ namespace Wox.Plugin
} }
public delegate void ActionKeywordsChangedEventHandler(IMultipleActionKeywords sender, ActionKeywordsChangedEventArgs e); 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<Result> Results;
public Query Query;
}
} }

View file

@ -7,6 +7,7 @@ namespace Wox.Plugin
public class Result public class Result
{ {
private string _pluginDirectory; private string _pluginDirectory;
private string _icoPath; private string _icoPath;
public string Title { get; set; } public string Title { get; set; }

View file

@ -46,7 +46,6 @@ namespace Wox
API = new PublicAPIInstance(mainVM, mainVM._settings); API = new PublicAPIInstance(mainVM, mainVM._settings);
PluginManager.InitializePlugins(API, pluginsSettings); PluginManager.InitializePlugins(API, pluginsSettings);
Window = new MainWindow(mainVM._settings, mainVM); Window = new MainWindow(mainVM._settings, mainVM);
NotifyIconManager notifyIconManager = new NotifyIconManager(API); NotifyIconManager notifyIconManager = new NotifyIconManager(API);
CommandArgsFactory.Execute(e.Args.ToList()); CommandArgsFactory.Execute(e.Args.ToList());

View file

@ -79,154 +79,22 @@ namespace Wox.ViewModel
InitializeResultListBox(); InitializeResultListBox();
InitializeContextMenu(); InitializeContextMenu();
InitializeKeyCommands(); InitializeKeyCommands();
RegisterResultsUpdatedEvent();
} }
public void Save() private void RegisterResultsUpdatedEvent()
{ {
_settingsStorage.Save(); foreach (var pair in PluginManager.GetPluginsForInterface<IResultUpdated>())
_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
{ {
return _queryText; var plugin = (IResultUpdated) pair.Plugin;
} plugin.ResultsUpdated += (s, e) =>
set
{
_queryText = value;
OnPropertyChanged();
if (_ignoreTextChange)
{ {
_ignoreTextChange = false; PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query);
} UpdateResultView(e.Results, pair.Metadata, e.Query);
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 InitializeKeyCommands() 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() private void QueryContextMenu()
{ {
@ -569,6 +575,14 @@ namespace Wox.ViewModel
#region Public Methods #region Public Methods
public void Save()
{
_settingsStorage.Save();
_queryHistoryStorage.Save();
_userSelectedRecordStorage.Save();
_topMostRecordStorage.Save();
}
public void UpdateResultView(List<Result> list, PluginMetadata metadata, Query originQuery) public void UpdateResultView(List<Result> list, PluginMetadata metadata, Query originQuery)
{ {
_queryHasReturn = true; _queryHasReturn = true;