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
{
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<string> GetSuggestions(string query)

View File

@@ -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<string> GetSuggestions(string query)
{
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
{
public interface ISuggestionSource
{
List<string> 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<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.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<Settings> _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<Result> Query(Query query)
{
List<Result> results = new List<Result>();
_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> { result };
}
else
{
// todo use Task.Wait when .net upgraded
results.AddRange(ResultsFromSuggestions(keyword, subtitle, webSearch));
var results = new List<Result>();
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)
{
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;
}
}

View File

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

View File

@@ -30,7 +30,7 @@ namespace Wox.Core.Plugin
public static IPublicAPI API { private set; get; }
private static PluginsSettings _settings;
private static List<PluginMetadata> _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<Result>();
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<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)
{
return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign);

View File

@@ -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<Result> Results;
public Query Query;
}
}

View File

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

View File

@@ -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());

View File

@@ -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<IResultUpdated>())
{
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<Result> list, PluginMetadata metadata, Query originQuery)
{
_queryHasReturn = true;