Merge branch 'dev'

This commit is contained in:
Jeremy Wu 2020-01-18 12:10:38 +11:00
commit 21fae1f457
28 changed files with 707 additions and 340 deletions

View file

@ -191,9 +191,14 @@ namespace Wox.Plugin.Folder
if (incompleteName.StartsWith(">"))
{
searchOption = SearchOption.AllDirectories;
incompleteName = incompleteName.Substring(1);
// match everything before and after search term using supported wildcard '*', ie. *searchterm*
incompleteName = "*" + incompleteName.Substring(1);
}
var folderList = new List<Result>();
var fileList = new List<Result>();
try
{
// search folder and add results
@ -204,11 +209,14 @@ namespace Wox.Plugin.Folder
{
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
var result =
fileSystemInfo is DirectoryInfo
? CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, query)
: CreateFileResult(fileSystemInfo.FullName, query);
results.Add(result);
if(fileSystemInfo is DirectoryInfo)
{
folderList.Add(CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, query));
}
else
{
fileList.Add(CreateFileResult(fileSystemInfo.FullName, query));
}
}
}
catch (Exception e)
@ -223,7 +231,8 @@ namespace Wox.Plugin.Folder
throw;
}
return results;
// Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList();
}
private static Result CreateFileResult(string filePath, Query query)

View file

@ -1,12 +1,12 @@
WoX
===
![Maintenance](https://img.shields.io/maintenance/yes/2019)
![Maintenance](https://img.shields.io/maintenance/yes/2020)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/jjw24/wox)](https://github.com/jjw24/Wox/releases/latest)
![GitHub Release Date](https://img.shields.io/github/release-date/jjw24/wox)
![GitHub commits since latest release](https://img.shields.io/github/commits-since/jjw24/wox/v1.3.524)
[![Build Status](https://dev.azure.com/Wox-Launcher/Wox/_apis/build/status/jjw24.Wox?branchName=master)](https://dev.azure.com/Wox-Launcher/Wox/_build/latest?definitionId=1&branchName=master)
[![Github All Releases](https://img.shields.io/github/downloads/Wox-launcher/Wox/total.svg)](https://github.com/Wox-launcher/Wox/releases)
[![Github All Releases](https://img.shields.io/github/downloads/jjw24/Wox/total.svg)](https://github.com/jjw24/Wox/releases)
[![RamenBless](https://cdn.rawgit.com/LunaGao/BlessYourCodeTag/master/tags/ramen.svg)](https://github.com/LunaGao/BlessYourCodeTag)
**WoX** is a launcher for Windows that simply works. It's an alternative to [Alfred](https://www.alfredapp.com/) and [Launchy](http://www.launchy.net/). You can call it Windows omni-eXecutor if you want a long name.

View file

@ -16,18 +16,23 @@ using Wox.Infrastructure.Logger;
namespace Wox.Core
{
public static class Updater
public class Updater
{
private static readonly Internationalization Translater = InternationalizationManager.Instance;
public string GitHubRepository { get; }
public static async Task UpdateApp()
public Updater(string gitHubRepository)
{
GitHubRepository = gitHubRepository;
}
public async Task UpdateApp()
{
UpdateManager m;
UpdateInfo u;
try
{
m = await GitHubUpdateManager(Constant.Repository);
m = await GitHubUpdateManager(GitHubRepository);
}
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
{
@ -66,8 +71,8 @@ namespace Wox.Core
await m.ApplyReleases(u);
await m.CreateUninstallerRegistryEntry();
var newVersionTips = Translater.GetTranslation("newVersionTips");
newVersionTips = string.Format(newVersionTips, fr.Version);
var newVersionTips = this.NewVersinoTips(fr.Version.ToString());
MessageBox.Show(newVersionTips);
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
}
@ -90,7 +95,7 @@ namespace Wox.Core
}
/// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs
private static async Task<UpdateManager> GitHubUpdateManager(string repository)
private async Task<UpdateManager> GitHubUpdateManager(string repository)
{
var uri = new Uri(repository);
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
@ -109,7 +114,7 @@ namespace Wox.Core
return manager;
}
public static string NewVersinoTips(string version)
public string NewVersinoTips(string version)
{
var translater = InternationalizationManager.Instance;
var tips = string.Format(translater.GetTranslation("newVersionTips"), version);

View file

@ -13,6 +13,16 @@ namespace Wox.Infrastructure.Http
{
private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko";
static Http()
{
// need to be added so it would work on a win10 machine
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12
| SecurityProtocolType.Ssl3;
}
public static HttpProxy Proxy { private get; set; }
public static IWebProxy WebProxy()
{

View file

@ -39,6 +39,19 @@ namespace Wox.Infrastructure.Image
var contains = _data.ContainsKey(key);
return contains;
}
public int CacheSize()
{
return _data.Count;
}
/// <summary>
/// return the number of unique images in the cache (by reference not by checking images content)
/// </summary>
public int UniqueImagesInCache()
{
return _data.Values.Distinct().Count();
}
}
}

View file

@ -0,0 +1,49 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Wox.Infrastructure.Image
{
public interface IImageHashGenerator
{
string GetHashFromImage(ImageSource image);
}
public class ImageHashGenerator : IImageHashGenerator
{
public string GetHashFromImage(ImageSource imageSource)
{
if (!(imageSource is BitmapSource image))
{
return null;
}
try
{
using (var outStream = new MemoryStream())
{
// PngBitmapEncoder enc2 = new PngBitmapEncoder();
// enc2.Frames.Add(BitmapFrame.Create(tt));
var enc = new JpegBitmapEncoder();
var bitmapFrame = BitmapFrame.Create(image);
bitmapFrame.Freeze();
enc.Frames.Add(bitmapFrame);
enc.Save(outStream);
var byteArray = outStream.GetBuffer();
using (var sha1 = new SHA1CryptoServiceProvider())
{
var hash = Convert.ToBase64String(sha1.ComputeHash(byteArray));
return hash;
}
}
}
catch
{
return null;
}
}
}
}

View file

@ -14,6 +14,8 @@ namespace Wox.Infrastructure.Image
{
private static readonly ImageCache ImageCache = new ImageCache();
private static BinaryStorage<ConcurrentDictionary<string, int>> _storage;
private static readonly ConcurrentDictionary<string, string> GuidToKey = new ConcurrentDictionary<string, string>();
private static IImageHashGenerator _hashGenerator;
private static readonly string[] ImageExtensions =
@ -30,7 +32,8 @@ namespace Wox.Infrastructure.Image
public static void Initialize()
{
_storage = new BinaryStorage<ConcurrentDictionary<string, int>> ("Image");
_storage = new BinaryStorage<ConcurrentDictionary<string, int>>("Image");
_hashGenerator = new ImageHashGenerator();
ImageCache.Usage = _storage.TryLoad(new ConcurrentDictionary<string, int>());
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon })
@ -43,16 +46,12 @@ namespace Wox.Infrastructure.Image
{
Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () =>
{
ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(i =>
ImageCache.Usage.AsParallel().ForAll(x =>
{
var img = Load(i.Key);
if (img != null)
{
ImageCache[i.Key] = img;
}
Load(x.Key);
});
});
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>");
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
});
}
@ -61,31 +60,56 @@ namespace Wox.Infrastructure.Image
ImageCache.Cleanup();
_storage.Save(ImageCache.Usage);
}
public static ImageSource Load(string path, bool loadFullImage = false)
private class ImageResult
{
public ImageResult(ImageSource imageSource, ImageType imageType)
{
ImageSource = imageSource;
ImageType = imageType;
}
public ImageType ImageType { get; }
public ImageSource ImageSource { get; }
}
private enum ImageType
{
File,
Folder,
Data,
ImageFile,
Error,
Cache
}
private static ImageResult LoadInternal(string path, bool loadFullImage = false)
{
ImageSource image;
ImageType type = ImageType.Error;
try
{
if (string.IsNullOrEmpty(path))
{
return ImageCache[Constant.ErrorIcon];
return new ImageResult(ImageCache[Constant.ErrorIcon], ImageType.Error);
}
if (ImageCache.ContainsKey(path))
{
return ImageCache[path];
return new ImageResult(ImageCache[path], ImageType.Cache);
}
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
{
return new BitmapImage(new Uri(path));
var imageSource = new BitmapImage(new Uri(path));
imageSource.Freeze();
return new ImageResult(imageSource, ImageType.Data);
}
if (!Path.IsPathRooted(path))
{
path = Path.Combine(Constant.ProgramDirectory, "Images", Path.GetFileName(path));
}
if (Directory.Exists(path))
{
/* Directories can also have thumbnails instead of shell icons.
@ -94,14 +118,17 @@ namespace Wox.Infrastructure.Image
* Wox responsibility.
* - Solution: just load the icon
*/
type = ImageType.Folder;
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
Constant.ThumbnailSize, ThumbnailOptions.IconOnly);
}
else if (File.Exists(path))
{
var extension = Path.GetExtension(path).ToLower();
if (ImageExtensions.Contains(extension))
{
type = ImageType.ImageFile;
if (loadFullImage)
{
image = LoadFullImage(path);
@ -119,6 +146,7 @@ namespace Wox.Infrastructure.Image
}
else
{
type = ImageType.File;
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
Constant.ThumbnailSize, ThumbnailOptions.None);
}
@ -128,17 +156,50 @@ namespace Wox.Infrastructure.Image
image = ImageCache[Constant.ErrorIcon];
path = Constant.ErrorIcon;
}
ImageCache[path] = image;
image.Freeze();
if (type != ImageType.Error)
{
image.Freeze();
}
}
catch (System.Exception e)
{
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e);
type = ImageType.Error;
image = ImageCache[Constant.ErrorIcon];
ImageCache[path] = image;
}
return image;
return new ImageResult(image, type);
}
private static bool EnableImageHash = true;
public static ImageSource Load(string path, bool loadFullImage = false)
{
var imageResult = LoadInternal(path, loadFullImage);
var img = imageResult.ImageSource;
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
{ // we need to get image hash
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
if (hash != null)
{
if (GuidToKey.TryGetValue(hash, out string key))
{ // image already exists
img = ImageCache[key];
}
else
{ // new guid
GuidToKey[hash] = path;
}
}
// update cache
ImageCache[path] = img;
}
return img;
}
private static BitmapImage LoadFullImage(string path)

View file

@ -110,7 +110,6 @@ namespace Wox.Infrastructure.Image
try
{
return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
finally

View file

@ -11,18 +11,20 @@ namespace Wox.Infrastructure.Logger
{
public const string DirectoryName = "Logs";
public static string CurrentLogDirectory { get; private set; }
static Log()
{
var path = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version);
if (!Directory.Exists(path))
CurrentLogDirectory = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version);
if (!Directory.Exists(CurrentLogDirectory))
{
Directory.CreateDirectory(path);
Directory.CreateDirectory(CurrentLogDirectory);
}
var configuration = new LoggingConfiguration();
var target = new FileTarget();
configuration.AddTarget("file", target);
target.FileName = path.Replace(@"\", "/") + "/${shortdate}.txt";
target.FileName = CurrentLogDirectory.Replace(@"\", "/") + "/${shortdate}.txt";
#if DEBUG
var rule = new LoggingRule("*", LogLevel.Debug, target);
#else
@ -47,8 +49,53 @@ namespace Wox.Infrastructure.Logger
return valid;
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Error(string message)
[MethodImpl(MethodImplOptions.Synchronized)]
public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "")
{
if (string.IsNullOrWhiteSpace(className))
{
LogFaultyFormat($"Fail to specify a class name during logging of message: {message ?? "no message entered"}");
}
if (string.IsNullOrWhiteSpace(message))
{ // todo: not sure we really need that
LogFaultyFormat($"Fail to specify a message during logging");
}
if (!string.IsNullOrWhiteSpace(methodName))
{
className += "." + methodName;
}
ExceptionInternal(className, message, exception);
}
private static void ExceptionInternal(string classAndMethod, string message, System.Exception e)
{
var logger = LogManager.GetLogger(classAndMethod);
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
logger.Error("-------------------------- Begin exception --------------------------");
logger.Error(message);
do
{
logger.Error($"Exception full name:\n <{e.GetType().FullName}>");
logger.Error($"Exception message:\n <{e.Message}>");
logger.Error($"Exception stack trace:\n <{e.StackTrace}>");
logger.Error($"Exception source:\n <{e.Source}>");
logger.Error($"Exception target site:\n <{e.TargetSite}>");
logger.Error($"Exception HResult:\n <{e.HResult}>");
e = e.InnerException;
} while (e != null);
logger.Error("-------------------------- End exception --------------------------");
}
private static void LogInternal(string message, LogLevel level)
{
if (FormatValid(message))
{
@ -57,8 +104,8 @@ namespace Wox.Infrastructure.Logger
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
logger.Error(unprefixed);
System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}");
logger.Log(level, unprefixed);
}
else
{
@ -78,25 +125,7 @@ namespace Wox.Infrastructure.Logger
var parts = message.Split('|');
var prefix = parts[1];
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
logger.Error("-------------------------- Begin exception --------------------------");
logger.Error(unprefixed);
do
{
logger.Error($"Exception full name:\n <{e.GetType().FullName}>");
logger.Error($"Exception message:\n <{e.Message}>");
logger.Error($"Exception stack trace:\n <{e.StackTrace}>");
logger.Error($"Exception source:\n <{e.Source}>");
logger.Error($"Exception target site:\n <{e.TargetSite}>");
logger.Error($"Exception HResult:\n <{e.HResult}>");
e = e.InnerException;
} while (e != null);
logger.Error("-------------------------- End exception --------------------------");
ExceptionInternal(prefix, unprefixed, e);
}
else
{
@ -104,62 +133,29 @@ namespace Wox.Infrastructure.Logger
}
#endif
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Error(string message)
{
LogInternal(message, LogLevel.Error);
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Debug(string message)
{
if (FormatValid(message))
{
var parts = message.Split('|');
var prefix = parts[1];
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"DEBUG|{message}");
logger.Debug(unprefixed);
}
else
{
LogFaultyFormat(message);
}
LogInternal(message, LogLevel.Debug);
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Info(string message)
{
if (FormatValid(message))
{
var parts = message.Split('|');
var prefix = parts[1];
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"INFO|{message}");
logger.Info(unprefixed);
}
else
{
LogFaultyFormat(message);
}
LogInternal(message, LogLevel.Info);
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Warn(string message)
{
if (FormatValid(message))
{
var parts = message.Split('|');
var prefix = parts[1];
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"WARN|{message}");
logger.Warn(unprefixed);
}
else
{
LogFaultyFormat(message);
}
LogInternal(message, LogLevel.Warn);
}
}
}

View file

@ -6,13 +6,14 @@ using Wox.Infrastructure.Logger;
using Wox.Infrastructure.UserSettings;
using static Wox.Infrastructure.StringMatcher;
namespace Wox.Infrastructure
namespace Wox.Infrastructure
{
public static class StringMatcher
{
public static MatchOption DefaultMatchOption = new MatchOption();
public static string UserSettingSearchPrecision { get; set; }
public static SearchPrecisionScore UserSettingSearchPrecision { get; set; }
public static bool ShouldUsePinyin { get; set; }
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
@ -40,56 +41,104 @@ namespace Wox.Infrastructure
}
/// <summary>
/// refer to https://github.com/mattyork/fuzzy
/// Current method:
/// Character matching + substring matching;
/// 1. Query search string is split into substrings, separator is whitespace.
/// 2. Check each query substring's characters against full compare string,
/// 3. if a character in the substring is matched, loop back to verify the previous character.
/// 4. If previous character also matches, and is the start of the substring, update list.
/// 5. Once the previous character is verified, move on to the next character in the query substring.
/// 6. Move onto the next substring's characters until all substrings are checked.
/// 7. Consider success and move onto scoring if every char or substring without whitespaces matched
/// </summary>
public static MatchResult FuzzySearch(string query, string stringToCompare, MatchOption opt)
{
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult { Success = false };
query = query.Trim();
var len = stringToCompare.Length;
var compareString = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
var pattern = opt.IgnoreCase ? query.ToLower() : query;
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int currentQuerySubstringIndex = 0;
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
var currentQuerySubstringCharacterIndex = 0;
var sb = new StringBuilder(stringToCompare.Length + (query.Length * (opt.Prefix.Length + opt.Suffix.Length)));
var patternIdx = 0;
var firstMatchIndex = -1;
var firstMatchIndexInWord = -1;
var lastMatchIndex = 0;
char ch;
bool allQuerySubstringsMatched = false;
bool matchFoundInPreviousLoop = false;
bool allSubstringsContainedInCompareString = true;
var indexList = new List<int>();
for (var idx = 0; idx < len; idx++)
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
{
ch = stringToCompare[idx];
if (compareString[idx] == pattern[patternIdx])
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
{
if (firstMatchIndex < 0)
firstMatchIndex = idx;
lastMatchIndex = idx + 1;
indexList.Add(idx);
sb.Append(opt.Prefix + ch + opt.Suffix);
patternIdx += 1;
}
else
{
sb.Append(ch);
matchFoundInPreviousLoop = false;
continue;
}
// match success, append remain char
if (patternIdx == pattern.Length && (idx + 1) != compareString.Length)
if (firstMatchIndex < 0)
{
sb.Append(stringToCompare.Substring(idx + 1));
break;
// first matched char will become the start of the compared string
firstMatchIndex = compareStringIndex;
}
if (currentQuerySubstringCharacterIndex == 0)
{
// first letter of current word
matchFoundInPreviousLoop = true;
firstMatchIndexInWord = compareStringIndex;
}
else if (!matchFoundInPreviousLoop)
{
// we want to verify that there is not a better match if this is not a full word
// in order to do so we need to verify all previous chars are part of the pattern
var startIndexToVerify = compareStringIndex - currentQuerySubstringCharacterIndex;
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex, fullStringToCompareWithoutCase, currentQuerySubstring))
{
matchFoundInPreviousLoop = true;
// if it's the beginning character of the first query substring that is matched then we need to update start index
firstMatchIndex = currentQuerySubstringIndex == 0 ? startIndexToVerify : firstMatchIndex;
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, firstMatchIndexInWord, indexList);
}
}
lastMatchIndex = compareStringIndex + 1;
indexList.Add(compareStringIndex);
currentQuerySubstringCharacterIndex++;
// if finished looping through every character in the current substring
if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length)
{
// if any of the substrings was not matched then consider as all are not matched
allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
currentQuerySubstringIndex++;
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
if (allQuerySubstringsMatched)
break;
// otherwise move to the next query substring
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
currentQuerySubstringCharacterIndex = 0;
}
}
// return rendered string if we have a match for every char
if (patternIdx == pattern.Length)
// proceed to calculate score if every char or substring without whitespaces matched
if (allQuerySubstringsMatched)
{
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex);
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
var pinyinScore = ScoreForPinyin(stringToCompare, query);
var result = new MatchResult
@ -105,7 +154,44 @@ namespace Wox.Infrastructure
return new MatchResult { Success = false };
}
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen)
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
string fullStringToCompareWithoutCase, string currentQuerySubstring)
{
var allMatch = true;
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
{
if (fullStringToCompareWithoutCase[startIndexToVerify + indexToCheck] !=
currentQuerySubstring[indexToCheck])
{
allMatch = false;
}
}
return allMatch;
}
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
{
var updatedList = new List<int>();
indexList.RemoveAll(x => x >= firstMatchIndexInWord);
updatedList.AddRange(indexList);
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
{
updatedList.Add(startIndexToVerify + indexToCheck);
}
return updatedList;
}
private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength)
{
return currentQuerySubstringIndex >= querySubstringsLength;
}
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString)
{
// A match found near the beginning of a string is scored more than a match found near the end
// A match is scored more if the characters in the patterns are closer to each other,
@ -122,6 +208,13 @@ namespace Wox.Infrastructure
score += 10;
}
if (allSubstringsContainedInCompareString)
{
int count = query.Count(c => !char.IsWhiteSpace(c));
int factor = count < 4 ? 10 : 5;
score += factor * count;
}
return score;
}
@ -143,11 +236,11 @@ namespace Wox.Infrastructure
{
if (Alphabet.ContainsChinese(source))
{
var combination = Alphabet.PinyinComination(source);
var combination = Alphabet.PinyinComination(source);
var pinyinScore = combination
.Select(pinyin => FuzzySearch(target, string.Join("", pinyin)).Score)
.Max();
var acronymScore = combination.Select(Alphabet.Acronym)
var acronymScore = combination.Select(Alphabet.Acronym)
.Select(pinyin => FuzzySearch(target, pinyin).Score)
.Max();
var score = Math.Max(pinyinScore, acronymScore);
@ -162,7 +255,7 @@ namespace Wox.Infrastructure
{
return 0;
}
}
}
}
public class MatchResult
@ -178,6 +271,7 @@ namespace Wox.Infrastructure
/// The raw calculated search score without any search precision filtering applied.
/// </summary>
private int _rawScore;
public int RawScore
{
get { return _rawScore; }
@ -200,10 +294,7 @@ namespace Wox.Infrastructure
private bool IsSearchPrecisionScoreMet(int score)
{
var precisionScore = (SearchPrecisionScore)Enum.Parse(
typeof(SearchPrecisionScore),
UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString());
return score >= (int)precisionScore;
return score >= (int)UserSettingSearchPrecision;
}
private int ApplySearchPrecisionFilter(int score)
@ -214,22 +305,18 @@ namespace Wox.Infrastructure
public class MatchOption
{
public MatchOption()
{
Prefix = "";
Suffix = "";
IgnoreCase = true;
}
/// <summary>
/// prefix of match char, use for hightlight
/// </summary>
public string Prefix { get; set; }
[Obsolete("this is never used")]
public string Prefix { get; set; } = "";
/// <summary>
/// suffix of match char, use for hightlight
/// </summary>
public string Suffix { get; set; }
[Obsolete("this is never used")]
public string Suffix { get; set; } = "";
public bool IgnoreCase { get; set; }
public bool IgnoreCase { get; set; } = true;
}
}
}

View file

@ -36,14 +36,30 @@ namespace Wox.Infrastructure.UserSettings
}
private string _querySearchPrecision { get; set; } = StringMatcher.SearchPrecisionScore.Regular.ToString();
public string QuerySearchPrecision
internal StringMatcher.SearchPrecisionScore QuerySearchPrecision { get; private set; } = StringMatcher.SearchPrecisionScore.Regular;
public string QuerySearchPrecisionString
{
get { return _querySearchPrecision; }
get { return QuerySearchPrecision.ToString(); }
set
{
_querySearchPrecision = value;
StringMatcher.UserSettingSearchPrecision = value;
try
{
var precisionScore = (StringMatcher.SearchPrecisionScore)Enum
.Parse(typeof(StringMatcher.SearchPrecisionScore), value);
QuerySearchPrecision = precisionScore;
StringMatcher.UserSettingSearchPrecision = precisionScore;
}
catch (ArgumentException e)
{
Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e);
QuerySearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
StringMatcher.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
throw;
}
}
}

View file

@ -71,6 +71,7 @@
<Compile Include="Hotkey\InterceptKeys.cs" />
<Compile Include="Hotkey\KeyEvent.cs" />
<Compile Include="Image\ImageCache.cs" />
<Compile Include="Image\ImageHashGenerator.cs" />
<Compile Include="Image\ImageLoader.cs" />
<Compile Include="Image\ThumbnailReader.cs" />
<Compile Include="Logger\Log.cs" />

View file

@ -29,7 +29,6 @@ namespace Wox.Infrastructure
public static readonly string DataDirectory = DetermineDataDirectory();
public static readonly string PluginsDirectory = Path.Combine(DataDirectory, Plugins);
public static readonly string PreinstalledDirectory = Path.Combine(ProgramDirectory, Plugins);
public const string Repository = "https://github.com/Wox-launcher/Wox";
public const string Issue = "https://github.com/Wox-launcher/Wox/issues/new";
public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion;

View file

@ -4,7 +4,6 @@ using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using Wox.Infrastructure;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin;
namespace Wox.Test
@ -12,17 +11,25 @@ namespace Wox.Test
[TestFixture]
public class FuzzyMatcherTest
{
private const string Chrome = "Chrome";
private const string CandyCrushSagaFromKing = "Candy Crush Saga from King";
private const string HelpCureHopeRaiseOnMindEntityChrome = "Help cure hope raise on mind entity Chrome";
private const string UninstallOrChangeProgramsOnYourComputer = "Uninstall or change programs on your computer";
private const string LastIsChrome = "Last is chrome";
private const string OneOneOneOne = "1111";
private const string MicrosoftSqlServerManagementStudio = "Microsoft SQL Server Management Studio";
public List<string> GetSearchStrings()
=> new List<string>
{
"Chrome",
Chrome,
"Choose which programs you want Windows to use for activities like web browsing, editing photos, sending e-mail, and playing music.",
"Help cure hope raise on mind entity Chrome ",
"Candy Crush Saga from King",
"Uninstall or change programs on your computer",
HelpCureHopeRaiseOnMindEntityChrome,
CandyCrushSagaFromKing,
UninstallOrChangeProgramsOnYourComputer,
"Add, change, and manage fonts on your computer",
"Last is chrome",
"1111"
LastIsChrome,
OneOneOneOne
};
public List<int> GetPrecisionScores()
@ -76,17 +83,17 @@ namespace Wox.Test
Assert.True(scoreResult == 0);
}
[TestCase("chr")]
[TestCase("chrom")]
[TestCase("chrome")]
[TestCase("chrome")]
[TestCase("cand")]
[TestCase("cpywa")]
[TestCase("ccs")]
public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
{
var results = new List<Result>();
foreach (var str in GetSearchStrings())
{
results.Add(new Result
@ -94,7 +101,7 @@ namespace Wox.Test
Title = str,
Score = StringMatcher.FuzzySearch(searchTerm, str).Score
});
}
}
foreach (var precisionScore in GetPrecisionScores())
{
@ -114,78 +121,103 @@ namespace Wox.Test
}
}
[TestCase("chrome")]
public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring(string searchTerm)
[TestCase(Chrome, Chrome, 137)]
[TestCase(Chrome, LastIsChrome, 83)]
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 21)]
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 15)]
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, 56)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 79)]//double spacing intended
public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore)
{
var searchStrings = new List<string>
{
"Chrome",//SCORE: 107
"Last is chrome",//SCORE: 53
"Help cure hope raise on mind entity Chrome",//SCORE: 21
"Uninstall or change programs on your computer", //SCORE: 15
"Candy Crush Saga from King"//SCORE: 0
}
.OrderByDescending(x => x)
.ToList();
// When, Given
var rawScore = StringMatcher.FuzzySearch(queryString, compareString).RawScore;
var results = new List<Result>();
foreach (var str in searchStrings)
{
results.Add(new Result
{
Title = str,
Score = StringMatcher.FuzzySearch(searchTerm, str).RawScore
});
}
var orderedResults = results.OrderByDescending(x => x.Title).ToList();
Debug.WriteLine("");
Debug.WriteLine("###############################################");
Debug.WriteLine("SEARCHTERM: " + searchTerm);
foreach (var item in orderedResults)
{
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title);
}
Debug.WriteLine("###############################################");
Debug.WriteLine("");
Assert.IsTrue(orderedResults[0].Score == 15 && orderedResults[0].Title == searchStrings[0]);
Assert.IsTrue(orderedResults[1].Score == 53 && orderedResults[1].Title == searchStrings[1]);
Assert.IsTrue(orderedResults[2].Score == 21 && orderedResults[2].Title == searchStrings[2]);
Assert.IsTrue(orderedResults[3].Score == 107 && orderedResults[3].Title == searchStrings[3]);
Assert.IsTrue(orderedResults[4].Score == 0 && orderedResults[4].Title == searchStrings[4]);
// Should
Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
}
[TestCase("goo", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.None, true)]
[TestCase("ccs", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("cand", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("cand", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Google Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.None, true)]
[TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("cand", "Candy Crush Saga from King",StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(
string queryString,
string compareString,
int expectedPrecisionScore,
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
bool expectedPrecisionResult)
{
var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore;
StringMatcher.UserSettingSearchPrecision = expectedPrecisionString.ToString();
// When
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore;
// Given
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
Debug.WriteLine("");
Debug.WriteLine("###############################################");
Debug.WriteLine($"SearchTerm: {queryString} PrecisionLevelSetAt: {expectedPrecisionString} ({expectedPrecisionScore})");
Debug.WriteLine($"SCORE: {matchResult.Score.ToString()}, ComparedString: {compareString}");
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
Debug.WriteLine("###############################################");
Debug.WriteLine("");
var matchPrecisionResult = matchResult.IsSearchPrecisionScoreMet();
Assert.IsTrue(matchPrecisionResult == expectedPrecisionResult);
// Should
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
$"Query:{queryString}{Environment.NewLine} " +
$"Compare:{compareString}{Environment.NewLine}" +
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
$"Precision Score: {(int)expectedPrecisionScore}");
}
[TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("term", "Windows Terminal (Preview)", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sqlserv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql servman", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql serv man", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql studio", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("mic", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Shutdown", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("mssms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("a test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings(
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
bool expectedPrecisionResult)
{
// When
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore;
// Given
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
Debug.WriteLine("");
Debug.WriteLine("###############################################");
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
Debug.WriteLine("###############################################");
Debug.WriteLine("");
// Should
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
$"Query:{queryString}{Environment.NewLine} " +
$"Compare:{compareString}{Environment.NewLine}" +
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
$"Precision Score: {(int)expectedPrecisionScore}");
}
}
}
}

View file

@ -1,7 +1,19 @@
<?xml version="1.0"?>
<configuration>
<!--https://msdn.microsoft.com/en-us/library/dd409252(v=vs.110).aspx-->
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="Wox.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<runtime>
<loadFromRemoteSources enabled="true"/>
</runtime>
<applicationSettings>
<Wox.Properties.Settings>
<setting name="GithubRepo" serializeAs="String">
<value>https://github.com/Wox-launcher/Wox</value>
</setting>
</Wox.Properties.Settings>
</applicationSettings>
</configuration>

View file

@ -25,6 +25,7 @@ namespace Wox
private Settings _settings;
private MainViewModel _mainVM;
private SettingWindowViewModel _settingsVM;
private readonly Updater _updater = new Updater(Wox.Properties.Settings.Default.GithubRepo);
[STAThread]
public static void Main()
@ -50,7 +51,7 @@ namespace Wox
ImageLoader.Initialize();
_settingsVM = new SettingWindowViewModel();
_settingsVM = new SettingWindowViewModel(_updater);
_settings = _settingsVM.Settings;
Alphabet.Initialize(_settings);
@ -111,12 +112,12 @@ namespace Wox
var timer = new Timer(1000 * 60 * 60 * 5);
timer.Elapsed += async (s, e) =>
{
await Updater.UpdateApp();
await _updater.UpdateApp();
};
timer.Start();
// check updates on startup
await Updater.UpdateApp();
await _updater.UpdateApp();
}
});
}

View file

@ -87,4 +87,4 @@
</ContentControl>
</StackPanel>
</Border>
</Window>
</Window>

View file

@ -12,7 +12,7 @@ namespace Wox.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -22,5 +22,14 @@ namespace Wox.Properties {
return defaultInstance;
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("https://github.com/Wox-launcher/Wox")]
public string GithubRepo {
get {
return ((string)(this["GithubRepo"]));
}
}
}
}

View file

@ -1,7 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:_settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Wox.Properties" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="GithubRepo" Type="System.String" Scope="Application">
<Value Profile="(Default)">https://github.com/Wox-launcher/Wox</Value>
</Setting>
</Settings>
</SettingsFile>

View file

@ -23,7 +23,7 @@ namespace Wox
private void SetException(Exception exception)
{
string path = Path.Combine(Constant.DataDirectory, Log.DirectoryName, Constant.Version);
string path = Log.CurrentLogDirectory;
var directory = new DirectoryInfo(path);
var log = directory.GetFiles().OrderByDescending(f => f.LastWriteTime).First();

View file

@ -62,7 +62,7 @@
<TextBlock Text="{DynamicResource querySearchPrecision}" />
<ComboBox Margin="10 0 0 0" Width="120"
ItemsSource="{Binding QuerySearchPrecisionStrings}"
SelectedItem="{Binding Settings.QuerySearchPrecision}" />
SelectedItem="{Binding Settings.QuerySearchPrecisionString}" />
</StackPanel>
<StackPanel Margin="10" Orientation="Horizontal">
<TextBlock Text="{DynamicResource lastQueryMode}" />

View file

@ -260,53 +260,16 @@ namespace Wox
#region Proxy
private void OnTestProxyClick(object sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(_settings.Proxy.Server))
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty"));
return;
}
if (_settings.Proxy.Port <= 0)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("portCantBeEmpty"));
return;
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Infrastructure.Constant.Repository);
if (string.IsNullOrEmpty(_settings.Proxy.UserName) || string.IsNullOrEmpty(_settings.Proxy.Password))
{
request.Proxy = new WebProxy(_settings.Proxy.Server, _settings.Proxy.Port);
}
else
{
request.Proxy = new WebProxy(_settings.Proxy.Server, _settings.Proxy.Port)
{
Credentials = new NetworkCredential(_settings.Proxy.UserName, _settings.Proxy.Password)
};
}
try
{
var response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyIsCorrect"));
}
else
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"));
}
}
catch
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"));
}
{ // TODO: change to command
var msg = _viewModel.TestProxy();
MessageBox.Show(msg); // TODO: add message box service
}
#endregion
private async void OnCheckUpdates(object sender, RoutedEventArgs e)
{
await Updater.UpdateApp();
_viewModel.UpdateApp(); // TODO: change to command
}
private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)

28
Wox/Settings.cs Normal file
View file

@ -0,0 +1,28 @@
namespace Wox.Properties {
// This class allows you to handle specific events on the settings class:
// The SettingChanging event is raised before a setting's value is changed.
// The PropertyChanged event is raised after a setting's value is changed.
// The SettingsLoaded event is raised after the setting values are loaded.
// The SettingsSaving event is raised before the setting values are saved.
internal sealed partial class Settings {
public Settings() {
// // To add event handlers for saving and changing settings, uncomment the lines below:
//
// this.SettingChanging += this.SettingChangingEventHandler;
//
// this.SettingsSaving += this.SettingsSavingEventHandler;
//
}
private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
// Add code to handle the SettingChangingEvent event here.
}
private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
// Add code to handle the SettingsSaving event here.
}
}
}

View file

@ -13,7 +13,6 @@ using Wox.Core.Resource;
using Wox.Helper;
using Wox.Infrastructure;
using Wox.Infrastructure.Hotkey;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.Storage;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin;
@ -25,7 +24,7 @@ namespace Wox.ViewModel
{
#region Private Fields
private bool _queryHasReturn;
private bool _isQueryRunning;
private Query _lastQuery;
private string _queryTextBeforeLeaveResults;
@ -41,7 +40,7 @@ namespace Wox.ViewModel
private CancellationToken _updateToken;
private bool _saved;
private Internationalization _translator = InternationalizationManager.Instance;
private readonly Internationalization _translator = InternationalizationManager.Instance;
#endregion
@ -312,7 +311,7 @@ namespace Wox.ViewModel
{
var filtered = results.Where
(
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet()
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet()
|| StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet()
).ToList();
ContextMenu.AddResults(filtered, id);
@ -371,63 +370,59 @@ namespace Wox.ViewModel
if (!string.IsNullOrEmpty(QueryText))
{
_updateSource?.Cancel();
_updateSource = new CancellationTokenSource();
_updateToken = _updateSource.Token;
var currentUpdateSource = new CancellationTokenSource();
_updateSource = currentUpdateSource;
var currentCancellationToken = _updateSource.Token;
_updateToken = currentCancellationToken;
ProgressBarVisibility = Visibility.Hidden;
_queryHasReturn = false;
_isQueryRunning = true;
var query = PluginManager.QueryInit(QueryText.Trim());
if (query != null)
{
// handle the exclusiveness of plugin using action keyword
string lastKeyword = _lastQuery.ActionKeyword;
string keyword = query.ActionKeyword;
if (string.IsNullOrEmpty(lastKeyword))
{
if (!string.IsNullOrEmpty(keyword))
{
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
}
}
else
{
if (string.IsNullOrEmpty(keyword))
{
Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata);
}
else if (lastKeyword != keyword)
{
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
}
}
RemoveOldQueryResults(query);
_lastQuery = query;
Task.Delay(200, _updateToken).ContinueWith(_ =>
{
if (query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn)
Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
{ // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
if (currentUpdateSource == _updateSource && _isQueryRunning)
{
ProgressBarVisibility = Visibility.Visible;
}
}, _updateToken);
}, currentCancellationToken);
var plugins = PluginManager.ValidPluginsForQuery(query);
Task.Run(() =>
{
Parallel.ForEach(plugins, plugin =>
// so looping will stop once it was cancelled
var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken };
try
{
var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID];
if (!config.Disabled)
Parallel.ForEach(plugins, parallelOptions, plugin =>
{
var results = PluginManager.QueryForPlugin(plugin, query);
UpdateResultView(results, plugin.Metadata, query);
}
});
var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID];
if (!config.Disabled)
{
var results = PluginManager.QueryForPlugin(plugin, query);
UpdateResultView(results, plugin.Metadata, query);
}
});
}
catch (OperationCanceledException)
{
// nothing to do here
}
// this should happen once after all queries are done so progress bar should continue
// until the end of all querying
_queryHasReturn = true;
ProgressBarVisibility = Visibility.Hidden;
}, _updateToken);
_isQueryRunning = false;
if (currentUpdateSource == _updateSource)
{ // update to hidden if this is still the current query
ProgressBarVisibility = Visibility.Hidden;
}
}, currentCancellationToken);
}
}
else
@ -437,6 +432,30 @@ namespace Wox.ViewModel
}
}
private void RemoveOldQueryResults(Query query)
{
string lastKeyword = _lastQuery.ActionKeyword;
string keyword = query.ActionKeyword;
if (string.IsNullOrEmpty(lastKeyword))
{
if (!string.IsNullOrEmpty(keyword))
{
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
}
}
else
{
if (string.IsNullOrEmpty(keyword))
{
Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata);
}
else if (lastKeyword != keyword)
{
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
}
}
}
private Result ContextMenuTopMost(Result result)
{
@ -660,4 +679,4 @@ namespace Wox.ViewModel
#endregion
}
}
}

View file

@ -156,14 +156,14 @@ namespace Wox.ViewModel
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
{
var results = Results.ToList();
var newResults = newRawResults.Select(r => new ResultViewModel(r)).ToList();
var newResults = newRawResults.Select(r => new ResultViewModel(r)).ToList();
var oldResults = results.Where(r => r.Result.PluginID == resultId).ToList();
// Find the same results in A (old results) and B (new newResults)
var sameResults = oldResults
.Where(t1 => newResults.Any(x => x.Result.Equals(t1.Result)))
.ToList();
// remove result of relative complement of B in A
foreach (var result in oldResults.Except(sameResults))
{
@ -248,6 +248,10 @@ namespace Wox.ViewModel
}
}
/// <summary>
/// Update the results collection with new results, try to keep identical results
/// </summary>
/// <param name="newItems"></param>
public void Update(List<ResultViewModel> newItems)
{
int newCount = newItems.Count;
@ -259,7 +263,7 @@ namespace Wox.ViewModel
ResultViewModel oldResult = this[i];
ResultViewModel newResult = newItems[i];
if (!oldResult.Equals(newResult))
{
{ // result is not the same update it in the current index
this[i] = newResult;
}
else if (oldResult.Result.Score != newResult.Result.Score)
@ -286,4 +290,4 @@ namespace Wox.ViewModel
}
}
}
}
}

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@ -20,10 +21,12 @@ namespace Wox.ViewModel
{
public class SettingWindowViewModel : BaseModel
{
private readonly Updater _updater;
private readonly WoxJsonStorage<Settings> _storage;
public SettingWindowViewModel()
public SettingWindowViewModel(Updater updater)
{
_updater = updater;
_storage = new WoxJsonStorage<Settings>();
Settings = _storage.Load();
Settings.PropertyChanged += (s, e) =>
@ -39,6 +42,10 @@ namespace Wox.ViewModel
public Settings Settings { get; set; }
public async void UpdateApp()
{
await _updater.UpdateApp();
}
public void Save()
{
@ -73,7 +80,7 @@ namespace Wox.ViewModel
public List<string> QuerySearchPrecisionStrings
{
get
{
{
var precisionStrings = new List<string>();
var enumList = Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)).Cast<StringMatcher.SearchPrecisionScore>().ToList();
@ -88,6 +95,50 @@ namespace Wox.ViewModel
public List<Language> Languages => _translater.LoadAvailableLanguages();
public IEnumerable<int> MaxResultsRange => Enumerable.Range(2, 16);
public string TestProxy()
{
var proxyServer = Settings.Proxy.Server;
var proxyUserName = Settings.Proxy.UserName;
if (string.IsNullOrEmpty(proxyServer))
{
return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty");
}
if (Settings.Proxy.Port <= 0)
{
return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty");
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository);
if (string.IsNullOrEmpty(proxyUserName) || string.IsNullOrEmpty(Settings.Proxy.Password))
{
request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port);
}
else
{
request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port)
{
Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password)
};
}
try
{
var response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
return InternationalizationManager.Instance.GetTranslation("proxyIsCorrect");
}
else
{
return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed");
}
}
catch
{
return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed");
}
}
#endregion
#region plugin
@ -220,7 +271,7 @@ namespace Wox.ViewModel
},
new Result
{
Title = $"Open Source: {Constant.Repository}",
Title = $"Open Source: {_updater.GitHubRepository}",
SubTitle = "Please star it!"
}
};
@ -330,8 +381,8 @@ namespace Wox.ViewModel
#region about
public static string Github => Constant.Repository;
public static string ReleaseNotes => @"https://github.com/Wox-launcher/Wox/releases/latest";
public string Github => _updater.GitHubRepository;
public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest";
public static string Version => Constant.Version;
public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
#endregion

View file

@ -165,6 +165,7 @@
<Compile Include="ResultListBox.xaml.cs">
<DependentUpon>ResultListBox.xaml</DependentUpon>
</Compile>
<Compile Include="Settings.cs" />
<Compile Include="Storage\HistoryItem.cs" />
<Compile Include="Storage\QueryHistory.cs" />
<Compile Include="Storage\TopMostRecord.cs" />

View file

@ -8,7 +8,7 @@ trigger:
- dev
pool:
vmImage: 'vs2017-win2016' #'windows-latest'
vmImage: 'vs2017-win2016' #'due to windows SDK dependency for building UWP project'
variables:
solution: '**/*.sln'