From b4c61927f0d2324379c3fc3e498b2a11c44e26ae Mon Sep 17 00:00:00 2001 From: Alekhya Reddy Date: Wed, 24 Jun 2020 12:45:31 +1000 Subject: [PATCH 001/442] Remove Program Plugin dll (ShObjldITIb.dll) --- .../Programs/ApplicationActivationHelper.cs | 44 + .../Programs/ShellLinkHelper.cs | 132 +++ .../Programs/UWP.cs | 5 +- .../Programs/Win32.cs | 32 +- .../ShObjIdlTlb.dll | Bin 144896 -> 0 bytes installer/PowerToysSetup/Product.wxs | 937 ++++++++++++++++++ 6 files changed, 1129 insertions(+), 21 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs delete mode 100644 Plugins/Flow.Launcher.Plugin.Program/ShObjIdlTlb.dll create mode 100644 installer/PowerToysSetup/Product.wxs diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs new file mode 100644 index 000000000..989995f31 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs @@ -0,0 +1,44 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Microsoft.Plugin.Program.Programs +{ + public class ApplicationActivationHelper + { + // Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs + public enum ActivateOptions + { + None = 0x00000000, + DesignMode = 0x00000001, + NoErrorUI = 0x00000002, + NoSplashScreen = 0x00000004, + } + + /// ApplicationActivationManager + [ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IApplicationActivationManager + { + IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId); + IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId); + IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId); + } + + // Application Activation Manager Class + [ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")] + public class ApplicationActivationManager : IApplicationActivationManager + { + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/] + public extern IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + public extern IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + public extern IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs new file mode 100644 index 000000000..49977393a --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; +using System.IO; +using Accessibility; +using System.Runtime.InteropServices.ComTypes; +using System.Security.Policy; + +namespace Microsoft.Plugin.Program.Programs +{ + class ShellLinkHelper + { + [Flags()] + public enum SLGP_FLAGS + { + SLGP_SHORTPATH = 0x1, + SLGP_UNCPRIORITY = 0x2, + SLGP_RAWPATH = 0x4 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct WIN32_FIND_DATAW + { + public uint dwFileAttributes; + public long ftCreationTime; + public long ftLastAccessTime; + public long ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string cFileName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + + [Flags()] + public enum SLR_FLAGS + { + SLR_NO_UI = 0x1, + SLR_ANY_MATCH = 0x2, + SLR_UPDATE = 0x4, + SLR_NOUPDATE = 0x8, + SLR_NOSEARCH = 0x10, + SLR_NOTRACK = 0x20, + SLR_NOLINKINFO = 0x40, + SLR_INVOKE_MSI = 0x80 + } + + + // Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW + /// The IShellLink interface allows Shell links to be created, modified, and resolved + [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")] + interface IShellLinkW + { + /// Retrieves the path and file name of a Shell link object + void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags); + /// Retrieves the list of item identifiers for a Shell link object + void GetIDList(out IntPtr ppidl); + /// Sets the pointer to an item identifier list (PIDL) for a Shell link object. + void SetIDList(IntPtr pidl); + /// Retrieves the description string for a Shell link object + void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); + /// Sets the description for a Shell link object. The description can be any application-defined string + void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); + /// Retrieves the name of the working directory for a Shell link object + void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); + /// Sets the name of the working directory for a Shell link object + void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); + /// Retrieves the command-line arguments associated with a Shell link object + void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); + /// Sets the command-line arguments for a Shell link object + void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); + /// Retrieves the hot key for a Shell link object + void GetHotkey(out short pwHotkey); + /// Sets a hot key for a Shell link object + void SetHotkey(short wHotkey); + /// Retrieves the show command for a Shell link object + void GetShowCmd(out int piShowCmd); + /// Sets the show command for a Shell link object. The show command sets the initial show state of the window. + void SetShowCmd(int iShowCmd); + /// Retrieves the location (path and index) of the icon for a Shell link object + void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, + int cchIconPath, out int piIcon); + /// Sets the location (path and index) of the icon for a Shell link object + void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); + /// Sets the relative path to the Shell link object + void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved); + /// Attempts to find the target of a Shell link, even if it has been moved or renamed + void Resolve(ref Accessibility._RemotableHandle hwnd, SLR_FLAGS fFlags); + /// Sets the path and file name of a Shell link object + void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); + } + + [ComImport(), Guid("00021401-0000-0000-C000-000000000046")] + public class ShellLink + { + } + + // To initialize the app description + public String description = String.Empty; + + + // Retrieve the target path using Shell Link + public string retrieveTargetPath(string path) + { + var link = new ShellLink(); + const int STGM_READ = 0; + ((IPersistFile)link).Load(path, STGM_READ); + var hwnd = new _RemotableHandle(); + ((IShellLinkW)link).Resolve(ref hwnd, 0); + + const int MAX_PATH = 260; + StringBuilder buffer = new StringBuilder(MAX_PATH); + + var data = new WIN32_FIND_DATAW(); + ((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH); + var target = buffer.ToString(); + + // To set the app description + if (!String.IsNullOrEmpty(target)) + { + buffer = new StringBuilder(MAX_PATH); + ((IShellLinkW)link).GetDescription(buffer, MAX_PATH); + description = buffer.ToString(); + } + return target; + } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index b8633f357..173cedd8d 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -13,7 +13,6 @@ using System.Xml.Linq; using Windows.ApplicationModel; using Windows.Management.Deployment; using AppxPackaing; -using Shell; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.Program.Logger; using IStream = AppxPackaing.IStream; @@ -337,10 +336,10 @@ namespace Flow.Launcher.Plugin.Program.Programs private async void Launch(IPublicAPI api) { - var appManager = new ApplicationActivationManager(); + var appManager = new ApplicationActivationHelper.ApplicationActivationManager(); uint unusedPid; const string noArgs = ""; - const ACTIVATEOPTIONS noFlags = ACTIVATEOPTIONS.AO_NONE; + const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None; await Task.Run(() => { try diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index cdea767f3..3b1da3abd 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -8,13 +8,14 @@ using System.Security; using System.Text; using System.Threading.Tasks; using Microsoft.Win32; -using Shell; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Plugin.Program.Logger; -using Flow.Launcher.Plugin.SharedCommands; +using Wox.Infrastructure; +using Microsoft.Plugin.Program.Logger; +using Wox.Plugin; +using System.Reflection; -namespace Flow.Launcher.Plugin.Program.Programs +namespace Microsoft.Plugin.Program.Programs { + [Serializable] public class Win32 : IProgram { @@ -190,27 +191,21 @@ namespace Flow.Launcher.Plugin.Program.Programs var program = Win32Program(path); try { - var link = new ShellLink(); - const uint STGM_READ = 0; - ((IPersistFile)link).Load(path, STGM_READ); - var hwnd = new _RemotableHandle(); - link.Resolve(ref hwnd, 0); - const int MAX_PATH = 260; StringBuilder buffer = new StringBuilder(MAX_PATH); + ShellLinkHelper _helper = new ShellLinkHelper(); + string target = _helper.retrieveTargetPath(path); - var data = new _WIN32_FIND_DATAW(); - const uint SLGP_SHORTPATH = 1; - link.GetPath(buffer, buffer.Capacity, ref data, SLGP_SHORTPATH); - var target = buffer.ToString(); if (!string.IsNullOrEmpty(target)) { var extension = Extension(target); if (extension == ExeExtension && File.Exists(target)) { - buffer = new StringBuilder(MAX_PATH); - link.GetDescription(buffer, MAX_PATH); - var description = buffer.ToString(); + program.LnkResolvedPath = program.FullPath; + program.FullPath = Path.GetFullPath(target).ToLower(); + program.ExecutableName = Path.GetFileName(target); + + var description = _helper.description; if (!string.IsNullOrEmpty(description)) { program.Description = description; @@ -276,6 +271,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var files = new List(); var folderQueue = new Queue(); folderQueue.Enqueue(directory); + do { var currentDirectory = folderQueue.Dequeue(); diff --git a/Plugins/Flow.Launcher.Plugin.Program/ShObjIdlTlb.dll b/Plugins/Flow.Launcher.Plugin.Program/ShObjIdlTlb.dll deleted file mode 100644 index 83815e40a4380f97638c8c0af35427eb4ae808e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144896 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
#2?NVUwg`D`1PSuV5rEjE7A#&LPJA1M7~+PKWd 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 D=|-o! diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs new file mode 100644 index 000000000..868acfbf1 --- /dev/null +++ b/installer/PowerToysSetup/Product.wxs @@ -0,0 +1,937 @@ + + + + + + + + + + + + + + + + + + + + + + = 17134)]]> + + + + + + + + + + + + + + + NOT Installed + 1 + NOT Installed + NOT Installed + Installed AND _REMOVE_ALL="Yes" + Installed AND _REMOVE_ALL="Yes" + Installed AND NOT (_REMOVE_ALL="Yes") + Installed AND NOT (_REMOVE_ALL="Yes") + + + + + + + + + + + + + + + + + + + + + + NOT Installed and CREATESCHEDULEDTASK = 1 + + + + + NOT Installed + + + Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + + NOT Installed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EXISTINGPOWERRENAMEEXTPATH OR EXISTINGIMAGERESIZERPATH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSTALLDESKTOPSHORTCUT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4f8ca9a453033187a71147758d378f4737d09d01 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 24 Jun 2020 12:55:21 +1000 Subject: [PATCH 002/442] update commit for Flow-Launcher --- .../Programs/ApplicationActivationHelper.cs | 2 +- .../Programs/ShellLinkHelper.cs | 2 +- .../Programs/Win32.cs | 10 +- installer/PowerToysSetup/Product.wxs | 937 ------------------ 4 files changed, 7 insertions(+), 944 deletions(-) delete mode 100644 installer/PowerToysSetup/Product.wxs diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs index 989995f31..3023836c1 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.RegularExpressions; -namespace Microsoft.Plugin.Program.Programs +namespace Flow.Launcher.Plugin.Program.Programs { public class ApplicationActivationHelper { diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs index 49977393a..4ded3412a 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs @@ -7,7 +7,7 @@ using Accessibility; using System.Runtime.InteropServices.ComTypes; using System.Security.Policy; -namespace Microsoft.Plugin.Program.Programs +namespace Flow.Launcher.Plugin.Program.Programs { class ShellLinkHelper { diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 3b1da3abd..f9fcc0897 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -8,12 +8,11 @@ using System.Security; using System.Text; using System.Threading.Tasks; using Microsoft.Win32; -using Wox.Infrastructure; -using Microsoft.Plugin.Program.Logger; -using Wox.Plugin; -using System.Reflection; +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Plugin.Program.Logger; +using Flow.Launcher.Plugin.SharedCommands; -namespace Microsoft.Plugin.Program.Programs +namespace Flow.Launcher.Plugin.Program.Programs { [Serializable] @@ -23,6 +22,7 @@ namespace Microsoft.Plugin.Program.Programs public string UniqueIdentifier { get; set; } public string IcoPath { get; set; } public string FullPath { get; set; } + public string LnkResolvedPath { get; set; } public string ParentDirectory { get; set; } public string ExecutableName { get; set; } public string Description { get; set; } diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs deleted file mode 100644 index 868acfbf1..000000000 --- a/installer/PowerToysSetup/Product.wxs +++ /dev/null @@ -1,937 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - = 17134)]]> - - - - - - - - - - - - - - - NOT Installed - 1 - NOT Installed - NOT Installed - Installed AND _REMOVE_ALL="Yes" - Installed AND _REMOVE_ALL="Yes" - Installed AND NOT (_REMOVE_ALL="Yes") - Installed AND NOT (_REMOVE_ALL="Yes") - - - - - - - - - - - - - - - - - - - - - - NOT Installed and CREATESCHEDULEDTASK = 1 - - - - - NOT Installed - - - Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") - - - - NOT Installed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - EXISTINGPOWERRENAMEEXTPATH OR EXISTINGIMAGERESIZERPATH - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSTALLDESKTOPSHORTCUT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 0342693442d5cce41c1ebd8537adbe9504116bbd Mon Sep 17 00:00:00 2001 From: Alekhya Reddy Date: Wed, 24 Jun 2020 21:34:33 +1000 Subject: [PATCH 003/442] Remove Program Plugin dll (AppxPackagingTlb.dll) --- .../AppxPackagingTlb.dll | Bin 17408 -> 0 bytes .../Flow.Launcher.Plugin.Program.csproj | 11 - .../Programs/AppxPackageHelper.cs | 82 ++ .../Programs/UWP.cs | 69 +- installer/PowerToysSetup/Product.wxs | 951 ++++++++++++++++++ 5 files changed, 1076 insertions(+), 37 deletions(-) delete mode 100644 Plugins/Flow.Launcher.Plugin.Program/AppxPackagingTlb.dll create mode 100644 Plugins/Flow.Launcher.Plugin.Program/Programs/AppxPackageHelper.cs create mode 100644 installer/PowerToysSetup/Product.wxs diff --git a/Plugins/Flow.Launcher.Plugin.Program/AppxPackagingTlb.dll b/Plugins/Flow.Launcher.Plugin.Program/AppxPackagingTlb.dll deleted file mode 100644 index 183cfc085c378e7b1927d3354d65d7206e15c978..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17408 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 HW#InPreserveNewest - - - - .\AppxPackagingTlb.dll - True - - - .\ShObjIdlTlb.dll - True - - diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/AppxPackageHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/AppxPackageHelper.cs new file mode 100644 index 000000000..f9605a574 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/AppxPackageHelper.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Windows.Storage; + +namespace Microsoft.Plugin.Program.Programs +{ + public class AppxPackageHelper + { + // This function returns a list of attributes of applications + public List getAppsFromManifest(IStream stream) + { + List apps = new List(); + var appxFactory = new AppxFactory(); + var reader = ((IAppxFactory)appxFactory).CreateManifestReader(stream); + var manifestApps = reader.GetApplications(); + while (manifestApps.GetHasCurrent()) + { + string appListEntry; + var manifestApp = manifestApps.GetCurrent(); + manifestApp.GetStringValue("AppListEntry", out appListEntry); + if (appListEntry != "none") + { + apps.Add(manifestApp); + } + manifestApps.MoveNext(); + } + return apps; + } + + // Reference : https://stackoverflow.com/questions/32122679/getting-icon-of-modern-windows-app-from-a-desktop-application + [Guid("5842a140-ff9f-4166-8f5c-62f5b7b0c781"), ComImport] + public class AppxFactory + { + } + + [Guid("BEB94909-E451-438B-B5A7-D79E767B75D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IAppxFactory + { + void _VtblGap0_2(); // skip 2 methods + IAppxManifestReader CreateManifestReader(IStream inputStream); + } + + [Guid("4E1BD148-55A0-4480-A3D1-15544710637C"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IAppxManifestReader + { + void _VtblGap0_1(); // skip 1 method + IAppxManifestProperties GetProperties(); + void _VtblGap1_5(); // skip 5 methods + IAppxManifestApplicationsEnumerator GetApplications(); + } + + [Guid("9EB8A55A-F04B-4D0D-808D-686185D4847A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IAppxManifestApplicationsEnumerator + { + IAppxManifestApplication GetCurrent(); + bool GetHasCurrent(); + bool MoveNext(); + } + + [Guid("5DA89BF4-3773-46BE-B650-7E744863B7E8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IAppxManifestApplication + { + [PreserveSig] + int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value); + + [PreserveSig] + int GetAppUserModelId([MarshalAs(UnmanagedType.LPWStr)] out string value); + } + + [Guid("03FAF64D-F26F-4B2C-AAF7-8FE7789B8BCA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IAppxManifestProperties + { + [PreserveSig] + int GetBoolValue([MarshalAs(UnmanagedType.LPWStr)]string name, out bool value); + [PreserveSig] + int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index b8633f357..1706bb606 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -12,14 +12,18 @@ using System.Windows.Media.Imaging; using System.Xml.Linq; using Windows.ApplicationModel; using Windows.Management.Deployment; -using AppxPackaing; -using Shell; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Plugin.Program.Logger; -using IStream = AppxPackaing.IStream; +using Wox.Infrastructure; +using Microsoft.Plugin.Program.Logger; using Rect = System.Windows.Rect; +using Windows.UI.Xaml.Media.Imaging; +using Windows.UI.Xaml.Media; +using System.Windows.Controls; +using Wox.Plugin; +using System.Reflection; +using Wox.Plugin.SharedCommands; +using System.Runtime.InteropServices.ComTypes; -namespace Flow.Launcher.Plugin.Program.Programs +namespace Microsoft.Plugin.Program.Programs { [Serializable] public class UWP @@ -51,12 +55,12 @@ namespace Flow.Launcher.Plugin.Program.Programs private void InitializeAppInfo() { + AppxPackageHelper _helper = new AppxPackageHelper(); var path = Path.Combine(Location, "AppxManifest.xml"); var namespaces = XmlNamespaces(path); InitPackageVersion(namespaces); - var appxFactory = new AppxFactory(); IStream stream; const uint noAttribute = 0x80; const Stgm exclusiveRead = Stgm.Read | Stgm.ShareExclusive; @@ -64,20 +68,15 @@ namespace Flow.Launcher.Plugin.Program.Programs if (hResult == Hresult.Ok) { - var reader = appxFactory.CreateManifestReader(stream); - var manifestApps = reader.GetApplications(); var apps = new List(); - while (manifestApps.GetHasCurrent() != 0) + + List _apps = _helper.getAppsFromManifest(stream); + foreach(var _app in _apps) { - var manifestApp = manifestApps.GetCurrent(); - var appListEntry = manifestApp.GetStringValue("AppListEntry"); - if (appListEntry != "none") - { - var app = new Application(manifestApp, this); - apps.Add(app); - } - manifestApps.MoveNext(); + var app = new Application(_app, this); + apps.Add(app); } + Apps = apps.Where(a => a.AppListEntry != "none").ToArray(); } else @@ -356,13 +355,30 @@ namespace Flow.Launcher.Plugin.Program.Programs }); } - public Application(IAppxManifestApplication manifestApp, UWP package) + public Application(AppxPackageHelper.IAppxManifestApplication manifestApp, UWP package) { - UserModelId = manifestApp.GetAppUserModelId(); - UniqueIdentifier = manifestApp.GetAppUserModelId(); - DisplayName = manifestApp.GetStringValue("DisplayName"); - Description = manifestApp.GetStringValue("Description"); - BackgroundColor = manifestApp.GetStringValue("BackgroundColor"); + // This is done because we cannot use the keyword 'out' along with a property + string tmpUserModelId; + string tmpUniqueIdentifier; + string tmpDisplayName; + string tmpDescription; + string tmpBackgroundColor; + string tmpEntryPoint; + + manifestApp.GetAppUserModelId(out tmpUserModelId); + manifestApp.GetAppUserModelId(out tmpUniqueIdentifier); + manifestApp.GetStringValue("DisplayName", out tmpDisplayName); + manifestApp.GetStringValue("Description", out tmpDescription); + manifestApp.GetStringValue("BackgroundColor", out tmpBackgroundColor); + manifestApp.GetStringValue("EntryPoint", out tmpEntryPoint); + + UserModelId = tmpUserModelId; + UniqueIdentifier = tmpUniqueIdentifier; + DisplayName = tmpDisplayName; + Description = tmpDescription; + BackgroundColor = tmpBackgroundColor; + EntryPoint = tmpEntryPoint; + Package = package; DisplayName = ResourceFromPri(package.FullName, package.Name, DisplayName); @@ -430,7 +446,7 @@ namespace Flow.Launcher.Plugin.Program.Programs return $"{prefix}//{packageName}{key}"; } - internal string LogoUriFromManifest(IAppxManifestApplication app) + internal string LogoUriFromManifest(AppxPackageHelper.IAppxManifestApplication app) { var logoKeyFromVersion = new Dictionary { @@ -440,8 +456,9 @@ namespace Flow.Launcher.Plugin.Program.Programs }; if (logoKeyFromVersion.ContainsKey(Package.Version)) { + string logoUri; var key = logoKeyFromVersion[Package.Version]; - var logoUri = app.GetStringValue(key); + app.GetStringValue(key, out logoUri); return logoUri; } else diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs new file mode 100644 index 000000000..2ff4c033f --- /dev/null +++ b/installer/PowerToysSetup/Product.wxs @@ -0,0 +1,951 @@ + + + + + + + + + + + + + + + + + + + + + + = 17134)]]> + + + + + + + + + + + + + + + NOT Installed + 1 + NOT Installed + NOT Installed + Installed AND _REMOVE_ALL="Yes" + Installed AND _REMOVE_ALL="Yes" + Installed AND NOT (_REMOVE_ALL="Yes") + Installed AND NOT (_REMOVE_ALL="Yes") + + + + + + + + + + + + + + + + + + + + + + NOT Installed and CREATESCHEDULEDTASK = 1 + + + + + NOT Installed + + + Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + + NOT Installed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EXISTINGPOWERRENAMEEXTPATH OR EXISTINGIMAGERESIZERPATH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSTALLDESKTOPSHORTCUT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 327e2f32531afdffd9262a1970773a7055b5b6e0 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 24 Jun 2020 21:55:52 +1000 Subject: [PATCH 004/442] update commit for Flow-Launcher --- .../Programs/AppxPackageHelper.cs | 2 +- .../Programs/UWP.cs | 15 +- installer/PowerToysSetup/Product.wxs | 951 ------------------ 3 files changed, 6 insertions(+), 962 deletions(-) delete mode 100644 installer/PowerToysSetup/Product.wxs diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/AppxPackageHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/AppxPackageHelper.cs index f9605a574..e48f30757 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/AppxPackageHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/AppxPackageHelper.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using Windows.Storage; -namespace Microsoft.Plugin.Program.Programs +namespace Flow.Launcher.Plugin.Program.Programs { public class AppxPackageHelper { diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 1706bb606..6818a8bcd 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Security.Principal; using System.Text; using System.Threading.Tasks; @@ -12,18 +13,11 @@ using System.Windows.Media.Imaging; using System.Xml.Linq; using Windows.ApplicationModel; using Windows.Management.Deployment; -using Wox.Infrastructure; -using Microsoft.Plugin.Program.Logger; +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Plugin.Program.Logger; using Rect = System.Windows.Rect; -using Windows.UI.Xaml.Media.Imaging; -using Windows.UI.Xaml.Media; -using System.Windows.Controls; -using Wox.Plugin; -using System.Reflection; -using Wox.Plugin.SharedCommands; -using System.Runtime.InteropServices.ComTypes; -namespace Microsoft.Plugin.Program.Programs +namespace Flow.Launcher.Plugin.Program.Programs { [Serializable] public class UWP @@ -253,6 +247,7 @@ namespace Microsoft.Plugin.Program.Programs public string UserModelId { get; set; } public string BackgroundColor { get; set; } + public string EntryPoint { get; set; } public string Name => DisplayName; public string Location => Package.Location; diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs deleted file mode 100644 index 2ff4c033f..000000000 --- a/installer/PowerToysSetup/Product.wxs +++ /dev/null @@ -1,951 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - = 17134)]]> - - - - - - - - - - - - - - - NOT Installed - 1 - NOT Installed - NOT Installed - Installed AND _REMOVE_ALL="Yes" - Installed AND _REMOVE_ALL="Yes" - Installed AND NOT (_REMOVE_ALL="Yes") - Installed AND NOT (_REMOVE_ALL="Yes") - - - - - - - - - - - - - - - - - - - - - - NOT Installed and CREATESCHEDULEDTASK = 1 - - - - - NOT Installed - - - Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") - - - - NOT Installed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - EXISTINGPOWERRENAMEEXTPATH OR EXISTINGIMAGERESIZERPATH - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSTALLDESKTOPSHORTCUT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 9c7dc054defe73dbdd917e16ef7d341be1dfaa5a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 25 Jun 2020 06:34:30 +1000 Subject: [PATCH 005/442] remove reference of dll in project file --- .../Flow.Launcher.Plugin.Program.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index 331566f90..fb3a894db 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -44,10 +44,6 @@ .\AppxPackagingTlb.dll True - - .\ShObjIdlTlb.dll - True - From b52dbbea53e7181e4eef49aeccbe7d3ab5de7d6e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 19 Aug 2020 21:02:46 +1000 Subject: [PATCH 006/442] launch win32 lnk programs using LnkResolvedPath use LnkResolvedPath which is the original lnk path, rather than using the FullPath property which is assigned with the path to the actual exe --- Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index f9fcc0897..f0de9f2c2 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -54,7 +54,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var result = new Result { - SubTitle = FullPath, + SubTitle = LnkResolvedPath ?? FullPath, IcoPath = IcoPath, Score = score, ContextData = this, @@ -62,7 +62,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { var info = new ProcessStartInfo { - FileName = FullPath, + FileName = LnkResolvedPath ?? FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true }; From 4ea5dd5ad2ad6d4869d57c8aed1e187d81662135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 13 Oct 2020 20:54:00 +0800 Subject: [PATCH 007/442] Move Old HttpWebRequest to HttpClient Instance (solve connection timeout) --- Flow.Launcher.Core/Updater.cs | 2 +- Flow.Launcher.Infrastructure/Http/Http.cs | 77 ++++++++++++++--------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 99d48275a..4ad8b19be 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -139,7 +139,7 @@ namespace Flow.Launcher.Core var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/"); - var client = new WebClient { Proxy = Http.WebProxy() }; + var client = new WebClient { Proxy = Http.WebProxy }; var downloader = new FileDownloader(client); var manager = new UpdateManager(latestUrl, urlDownloader: downloader); diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index b7d274205..038362a0d 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -13,6 +13,13 @@ namespace Flow.Launcher.Infrastructure.Http { private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko"; + private static HttpClient client; + private static SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler() + { + UseProxy = true, + Proxy = WebProxy + }; + static Http() { // need to be added so it would work on a win10 machine @@ -20,36 +27,55 @@ namespace Flow.Launcher.Infrastructure.Http ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; + + client.DefaultRequestHeaders.Add("User-Agent", UserAgent); + + } + private static HttpProxy proxy; + public static HttpProxy Proxy + { + private get + { + return proxy; + } + set + { + proxy = value; + UpdateProxy(); + } } - public static HttpProxy Proxy { private get; set; } - public static IWebProxy WebProxy() + public static WebProxy WebProxy { get; private set; } + + /// + /// Update the Address of the Proxy to modify the client Proxy + /// + public static void UpdateProxy() + // TODO: need test with a proxy { if (Proxy != null && Proxy.Enabled && !string.IsNullOrEmpty(Proxy.Server)) { if (string.IsNullOrEmpty(Proxy.UserName) || string.IsNullOrEmpty(Proxy.Password)) { - var webProxy = new WebProxy(Proxy.Server, Proxy.Port); - return webProxy; + WebProxy.Address = new Uri($"http://{Proxy.Server}:{Proxy.Port}"); + WebProxy.Credentials = null; } else { - var webProxy = new WebProxy(Proxy.Server, Proxy.Port) - { - Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password) - }; - return webProxy; + WebProxy.Address = new Uri($"http://{Proxy.Server}:{Proxy.Port}"); + WebProxy.Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password); } } else { - return WebRequest.GetSystemWebProxy(); + WebProxy.Address = new WebProxy().Address; + WebProxy.Credentials = null; } } public static void Download([NotNull] string url, [NotNull] string filePath) { - var client = new WebClient { Proxy = WebProxy() }; + var client = new WebClient { Proxy = WebProxy }; client.Headers.Add("user-agent", UserAgent); client.DownloadFile(url, filePath); } @@ -57,26 +83,17 @@ namespace Flow.Launcher.Infrastructure.Http public static async Task Get([NotNull] string url, string encoding = "UTF-8") { Log.Debug($"|Http.Get|Url <{url}>"); - var request = WebRequest.CreateHttp(url); - request.Method = "GET"; - request.Timeout = 1000; - request.Proxy = WebProxy(); - request.UserAgent = UserAgent; - var response = await request.GetResponseAsync() as HttpWebResponse; - response = response.NonNull(); - var stream = response.GetResponseStream().NonNull(); - - using (var reader = new StreamReader(stream, Encoding.GetEncoding(encoding))) + var response = await client.GetAsync(url); + using var stream = await response.Content.ReadAsStreamAsync(); + using var reader = new StreamReader(stream, Encoding.GetEncoding(encoding)); + var content = await reader.ReadToEndAsync(); + if (response.StatusCode == HttpStatusCode.OK) { - var content = await reader.ReadToEndAsync(); - if (response.StatusCode == HttpStatusCode.OK) - { - return content; - } - else - { - throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); - } + return content; + } + else + { + throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); } } } From a16cc5be8db3b5d52acf12565b029439c8d1a921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 13 Oct 2020 20:54:00 +0800 Subject: [PATCH 008/442] Move Old HttpWebRequest to HttpClient Instance (solve connection timeout) --- Flow.Launcher.Core/Updater.cs | 2 +- Flow.Launcher.Infrastructure/Http/Http.cs | 78 ++++++++++++++--------- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 99d48275a..4ad8b19be 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -139,7 +139,7 @@ namespace Flow.Launcher.Core var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/"); - var client = new WebClient { Proxy = Http.WebProxy() }; + var client = new WebClient { Proxy = Http.WebProxy }; var downloader = new FileDownloader(client); var manager = new UpdateManager(latestUrl, urlDownloader: downloader); diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index b7d274205..81c07eff7 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; +using System; namespace Flow.Launcher.Infrastructure.Http { @@ -13,6 +14,13 @@ namespace Flow.Launcher.Infrastructure.Http { private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko"; + private static HttpClient client; + private static SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler() + { + UseProxy = true, + Proxy = WebProxy + }; + static Http() { // need to be added so it would work on a win10 machine @@ -20,36 +28,55 @@ namespace Flow.Launcher.Infrastructure.Http ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; + + client.DefaultRequestHeaders.Add("User-Agent", UserAgent); + + } + private static HttpProxy proxy; + public static HttpProxy Proxy + { + private get + { + return proxy; + } + set + { + proxy = value; + UpdateProxy(); + } } - public static HttpProxy Proxy { private get; set; } - public static IWebProxy WebProxy() + public static WebProxy WebProxy { get; private set; } + + /// + /// Update the Address of the Proxy to modify the client Proxy + /// + public static void UpdateProxy() + // TODO: need test with a proxy { if (Proxy != null && Proxy.Enabled && !string.IsNullOrEmpty(Proxy.Server)) { if (string.IsNullOrEmpty(Proxy.UserName) || string.IsNullOrEmpty(Proxy.Password)) { - var webProxy = new WebProxy(Proxy.Server, Proxy.Port); - return webProxy; + WebProxy.Address = new Uri($"http://{Proxy.Server}:{Proxy.Port}"); + WebProxy.Credentials = null; } else { - var webProxy = new WebProxy(Proxy.Server, Proxy.Port) - { - Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password) - }; - return webProxy; + WebProxy.Address = new Uri($"http://{Proxy.Server}:{Proxy.Port}"); + WebProxy.Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password); } } else { - return WebRequest.GetSystemWebProxy(); + WebProxy.Address = new WebProxy().Address; + WebProxy.Credentials = null; } } public static void Download([NotNull] string url, [NotNull] string filePath) { - var client = new WebClient { Proxy = WebProxy() }; + var client = new WebClient { Proxy = WebProxy }; client.Headers.Add("user-agent", UserAgent); client.DownloadFile(url, filePath); } @@ -57,26 +84,17 @@ namespace Flow.Launcher.Infrastructure.Http public static async Task Get([NotNull] string url, string encoding = "UTF-8") { Log.Debug($"|Http.Get|Url <{url}>"); - var request = WebRequest.CreateHttp(url); - request.Method = "GET"; - request.Timeout = 1000; - request.Proxy = WebProxy(); - request.UserAgent = UserAgent; - var response = await request.GetResponseAsync() as HttpWebResponse; - response = response.NonNull(); - var stream = response.GetResponseStream().NonNull(); - - using (var reader = new StreamReader(stream, Encoding.GetEncoding(encoding))) + var response = await client.GetAsync(url); + using var stream = await response.Content.ReadAsStreamAsync(); + using var reader = new StreamReader(stream, Encoding.GetEncoding(encoding)); + var content = await reader.ReadToEndAsync(); + if (response.StatusCode == HttpStatusCode.OK) { - var content = await reader.ReadToEndAsync(); - if (response.StatusCode == HttpStatusCode.OK) - { - return content; - } - else - { - throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); - } + return content; + } + else + { + throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); } } } From c55e889f4fb37aa2624eec4523cbcd94cadc3293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 14 Oct 2020 11:29:12 +0800 Subject: [PATCH 009/442] Change Download to HttpClient as well (which change it to async as well) --- Flow.Launcher.Infrastructure/Http/Http.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 81c07eff7..34665217c 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -29,6 +29,7 @@ namespace Flow.Launcher.Infrastructure.Http | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; + client = new HttpClient(socketsHttpHandler, false); client.DefaultRequestHeaders.Add("User-Agent", UserAgent); } @@ -46,7 +47,7 @@ namespace Flow.Launcher.Infrastructure.Http } } - public static WebProxy WebProxy { get; private set; } + public static WebProxy WebProxy { get; private set; } = new WebProxy(); /// /// Update the Address of the Proxy to modify the client Proxy @@ -74,11 +75,18 @@ namespace Flow.Launcher.Infrastructure.Http } } - public static void Download([NotNull] string url, [NotNull] string filePath) + public async static Task Download([NotNull] string url, [NotNull] string filePath) { - var client = new WebClient { Proxy = WebProxy }; - client.Headers.Add("user-agent", UserAgent); - client.DownloadFile(url, filePath); + using var response = await client.GetAsync(url); + if (response.StatusCode == HttpStatusCode.OK) + { + using var fileStream = new FileStream(filePath, FileMode.CreateNew); + await response.Content.CopyToAsync(fileStream); + } + else + { + throw new WebException($"Error code <{response.StatusCode}> returned from <{url}>"); + } } public static async Task Get([NotNull] string url, string encoding = "UTF-8") From 424d757add58ccb38b50332b8a7597e8768b84bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 14 Oct 2020 11:29:38 +0800 Subject: [PATCH 010/442] Add Task.Run due to the change of async download --- .../Main.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs index e1b631517..0255216a0 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs @@ -144,7 +144,7 @@ namespace Flow.Launcher.Plugin.PluginManagement IcoPath = "Images\\plugin.png", TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.name).MatchData, SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.description).MatchData, - Action = c => + Action = _ => { MessageBoxResult result = MessageBox.Show("Are you sure you wish to install the \'" + r.name + "\' plugin", "Install plugin", MessageBoxButton.YesNo); @@ -157,17 +157,19 @@ namespace Flow.Launcher.Plugin.PluginManagement string pluginUrl = APIBASE + "/media/" + r1.plugin_file; - try + Task.Run(async () => { - Http.Download(pluginUrl, filePath); - } - catch (WebException e) - { - context.API.ShowMsg($"PluginManagement.ResultForInstallPlugin: download failed for <{r.name}>"); - Log.Exception($"|PluginManagement.ResultForInstallPlugin|download failed for <{r.name}>", e); - return false; - } - context.API.InstallPlugin(filePath); + try + { + await Http.Download(pluginUrl, filePath); + context.API.InstallPlugin(filePath); + } + catch (WebException e) + { + context.API.ShowMsg($"PluginManagement.ResultForInstallPlugin: download failed for <{r.name}>"); + Log.Exception($"|PluginManagement.ResultForInstallPlugin|download failed for <{r.name}>", e); + } + }); } return false; } From c63c98645cde289842ecabbc39b92ae2ddb3b277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 18 Oct 2020 21:17:29 +0800 Subject: [PATCH 011/442] Add Acronym Support for Fuzzy Search --- Flow.Launcher.Infrastructure/StringMatcher.cs | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 2a4270fb4..f15e22494 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -54,9 +54,55 @@ namespace Flow.Launcher.Infrastructure stringToCompare = _alphabet.Translate(stringToCompare); } + // This also can be done by spliting the query + + //(var spaceSplit, var upperSplit) = stringToCompare switch + //{ + // string s when s.Contains(' ') => (s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(w => w.First()), + // default(IEnumerable)), + // string s when s.Any(c => char.IsUpper(c)) && s.Any(c => char.IsLower(c)) => + // (null, Regex.Split(s, @"(? w.First())), + // _ => ((IEnumerable)null, (IEnumerable)null) + //}; + + var currentQueryIndex = 0; + var acronymMatchData = new List(); + var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; + + int acronymScore = 100; + + for (int compareIndex = 0; compareIndex < stringToCompare.Length; compareIndex++) + { + if (currentQueryIndex >= queryWithoutCase.Length) + break; + + if (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex])) + { + acronymMatchData.Add(compareIndex); + currentQueryIndex++; + continue; + } + + switch (stringToCompare[compareIndex]) + { + case char c when (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) + || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]): + acronymMatchData.Add(compareIndex); + currentQueryIndex++; + continue; + + case char c when char.IsWhiteSpace(c): + compareIndex++; + acronymScore -= 10; + break; + case char c when char.IsUpper(c): + acronymScore -= 10; + break; + } + } + var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; - var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); int currentQuerySubstringIndex = 0; From e264af500fa129e74e6f22c6a2a283bf7d11560c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 18 Oct 2020 21:24:59 +0800 Subject: [PATCH 012/442] merge one extra condition to the switch case --- Flow.Launcher.Infrastructure/StringMatcher.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index f15e22494..cca6388f9 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -76,16 +76,11 @@ namespace Flow.Launcher.Infrastructure if (currentQueryIndex >= queryWithoutCase.Length) break; - if (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex])) - { - acronymMatchData.Add(compareIndex); - currentQueryIndex++; - continue; - } switch (stringToCompare[compareIndex]) { - case char c when (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) + case char c when compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex]) + || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]): acronymMatchData.Add(compareIndex); currentQueryIndex++; From 9ad78387293b2b3574487d32edabadd50cce055a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 18 Oct 2020 21:24:59 +0800 Subject: [PATCH 013/442] merge one extra condition to the switch case --- Flow.Launcher.Infrastructure/StringMatcher.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index f15e22494..6d70fff30 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -76,16 +76,11 @@ namespace Flow.Launcher.Infrastructure if (currentQueryIndex >= queryWithoutCase.Length) break; - if (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex])) - { - acronymMatchData.Add(compareIndex); - currentQueryIndex++; - continue; - } switch (stringToCompare[compareIndex]) { - case char c when (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) + case char c when (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex])) + || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]): acronymMatchData.Add(compareIndex); currentQueryIndex++; From 468f8899b95d40764a941afd0eecff251ee5dc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 18 Oct 2020 21:31:08 +0800 Subject: [PATCH 014/442] Add return statement.... --- Flow.Launcher.Infrastructure/StringMatcher.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 6d70fff30..d09bcaf26 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -96,6 +96,9 @@ namespace Flow.Launcher.Infrastructure } } + if (acronymMatchData.Count == query.Length && acronymScore >= 60) + return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); + var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; From 4d06187fa561bad1e126d513bce15b67d1d3d114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 20 Oct 2020 08:28:05 +0800 Subject: [PATCH 015/442] use ?. and ?? instead of if == null --- Flow.Launcher.Infrastructure/StringMatcher.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index d09bcaf26..7b44b09c9 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -48,11 +48,7 @@ namespace Flow.Launcher.Infrastructure query = query.Trim(); - if (_alphabet != null) - { - query = _alphabet.Translate(query); - stringToCompare = _alphabet.Translate(stringToCompare); - } + stringToCompare = _alphabet?.Translate(stringToCompare) ?? stringToCompare; // This also can be done by spliting the query From 706f30a3a2c3b0aa74b67c30c0c42a41fe1fcd5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 24 Oct 2020 17:45:31 +0800 Subject: [PATCH 016/442] Add number support (treat number as part of acronuym) --- Flow.Launcher.Infrastructure/StringMatcher.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 7b44b09c9..96391f1d6 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -44,8 +44,8 @@ namespace Flow.Launcher.Infrastructure /// public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt) { - if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult (false, UserSettingSearchPrecision); - + if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult(false, UserSettingSearchPrecision); + query = query.Trim(); stringToCompare = _alphabet?.Translate(stringToCompare) ?? stringToCompare; @@ -77,7 +77,8 @@ namespace Flow.Launcher.Infrastructure { case char c when (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex])) || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) - || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]): + || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]) + || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): acronymMatchData.Add(compareIndex); currentQueryIndex++; continue; @@ -86,7 +87,7 @@ namespace Flow.Launcher.Infrastructure compareIndex++; acronymScore -= 10; break; - case char c when char.IsUpper(c): + case char c when char.IsUpper(c) || char.IsNumber(c): acronymScore -= 10; break; } @@ -97,7 +98,7 @@ namespace Flow.Launcher.Infrastructure var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; - + var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); int currentQuerySubstringIndex = 0; var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; @@ -180,7 +181,7 @@ namespace Flow.Launcher.Infrastructure currentQuerySubstringCharacterIndex = 0; } } - + // proceed to calculate score if every char or substring without whitespaces matched if (allQuerySubstringsMatched) { @@ -223,7 +224,7 @@ namespace Flow.Launcher.Infrastructure return allMatch; } - + private static List GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List indexList) { var updatedList = new List(); From 9d2164962292104aa9f33bdaef74fb8871208549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 10 Nov 2020 17:13:45 +0800 Subject: [PATCH 017/442] Change UI rendering logic Co-authored-by: Bao-Qian --- Flow.Launcher/ResultListBox.xaml | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 24 +++++- Flow.Launcher/ViewModel/ResultsForUpdate.cs | 36 +++++++++ Flow.Launcher/ViewModel/ResultsViewModel.cs | 89 ++++++++------------- 4 files changed, 92 insertions(+), 59 deletions(-) create mode 100644 Flow.Launcher/ViewModel/ResultsForUpdate.cs diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 3280dc457..60d94a37b 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -42,7 +42,7 @@ + Source="{Binding Image}" /> diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f18b74022..ac2cc79d5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -21,6 +21,7 @@ using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; using System.Windows.Media; using Flow.Launcher.Infrastructure.Image; +using System.Collections.Concurrent; namespace Flow.Launcher.ViewModel { @@ -47,6 +48,8 @@ namespace Flow.Launcher.ViewModel private bool _saved; private readonly Internationalization _translator = InternationalizationManager.Instance; + private BlockingCollection _resultsUpdateQueue; + #endregion @@ -76,6 +79,8 @@ namespace Flow.Launcher.ViewModel InitializeKeyCommands(); RegisterResultsUpdatedEvent(); + RegisterResultUpdate(); + SetHotkey(_settings.Hotkey, OnHotkey); SetCustomPluginHotkey(); SetOpenResultModifiers(); @@ -91,12 +96,27 @@ namespace Flow.Launcher.ViewModel Task.Run(() => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - UpdateResultView(e.Results, pair.Metadata, e.Query); + _resultsUpdateQueue.Add(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); }, _updateToken); }; } } + private void RegisterResultUpdate() + { + _resultsUpdateQueue = new BlockingCollection(); + + Task.Run(() => + { + while (true) + { + var resultToUpdate = _resultsUpdateQueue.Take(); + if (!resultToUpdate.Token.IsCancellationRequested) + UpdateResultView(resultToUpdate.Results, resultToUpdate.Metadata, resultToUpdate.Query); + } + }); + } + private void InitializeKeyCommands() { @@ -415,7 +435,7 @@ namespace Flow.Launcher.ViewModel if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); - UpdateResultView(results, plugin.Metadata, query); + _resultsUpdateQueue.Add(new ResultsForUpdate(results, plugin.Metadata, query, _updateToken)); } }); } diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs new file mode 100644 index 000000000..29f626285 --- /dev/null +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -0,0 +1,36 @@ +using Flow.Launcher.Plugin; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Flow.Launcher.ViewModel +{ + class ResultsForUpdate + { + public List Results { get; } + + public PluginMetadata Metadata { get; } + public string ID { get; } + + public Query Query { get; } + public CancellationToken Token { get; } + + public ResultsForUpdate(List results, string resultID, CancellationToken token) + { + Results = results; + ID = resultID; + Token = token; + } + + + public ResultsForUpdate(List results, PluginMetadata metadata, Query query, CancellationToken token) + { + Results = results; + Metadata = metadata; + Query = query; + Token = token; + ID = metadata.ID; + } + } +} diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d30854180..ac435c494 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -17,7 +17,6 @@ namespace Flow.Launcher.ViewModel public ResultCollection Results { get; } - private readonly object _addResultsLock = new object(); private readonly object _collectionLock = new object(); private readonly Settings _settings; private int MaxResults => _settings?.MaxResultsToShow ?? 6; @@ -134,70 +133,37 @@ namespace Flow.Launcher.ViewModel /// public void AddResults(List newRawResults, string resultId) { - lock (_addResultsLock) + var newResults = NewResults(newRawResults, resultId); + + // update UI in one run, so it can avoid UI flickering + Results.Update(newResults); + + if (Results.Count > 0) { - var newResults = NewResults(newRawResults, resultId); - - // update UI in one run, so it can avoid UI flickering - Results.Update(newResults); - - if (Results.Count > 0) - { - Margin = new Thickness { Top = 8 }; - SelectedIndex = 0; - } - else - { - Margin = new Thickness { Top = 0 }; - } + Margin = new Thickness { Top = 8 }; + SelectedIndex = 0; + } + else + { + Margin = new Thickness { Top = 0 }; } } private List NewResults(List newRawResults, string resultId) { - var results = Results.ToList(); + if (newRawResults.Count == 0) + return Results.ToList(); + + var results = Results as IEnumerable; + var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).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)) - { - results.Remove(result); - } - - // update result with B's score and index position - foreach (var sameResult in sameResults) - { - int oldIndex = results.IndexOf(sameResult); - int oldScore = results[oldIndex].Result.Score; - var newResult = newResults[newResults.IndexOf(sameResult)]; - int newScore = newResult.Result.Score; - if (newScore != oldScore) - { - var oldResult = results[oldIndex]; - - oldResult.Result.Score = newScore; - oldResult.Result.OriginQuery = newResult.Result.OriginQuery; - - results.RemoveAt(oldIndex); - int newIndex = InsertIndexOf(newScore, results); - results.Insert(newIndex, oldResult); - } - } - - // insert result in relative complement of A in B - foreach (var result in newResults.Except(sameResults)) - { - int newIndex = InsertIndexOf(result.Result.Score, results); - results.Insert(newIndex, result); - } - - return results; + return results.Where(r => r.Result.PluginID != resultId) + .Concat(newResults) + .OrderByDescending(r => r.Result.Score) + .Take(MaxResults * 2) + .ToList(); } #endregion @@ -254,6 +220,17 @@ namespace Flow.Launcher.ViewModel /// public void Update(List newItems) { + ClearItems(); + + foreach (var item in newItems) + { + Add(item); + } + + + + return; + int newCount = newItems.Count; int oldCount = Items.Count; int location = newCount > oldCount ? oldCount : newCount; From 0e0f24f635bcbe4723692fe649c6ae475da34786 Mon Sep 17 00:00:00 2001 From: Qian Bao Date: Fri, 23 Oct 2020 10:45:22 +0800 Subject: [PATCH 018/442] Lazy Load Image --- Flow.Launcher/ResultListBox.xaml | 2 +- Flow.Launcher/ViewModel/ResultViewModel.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 60d94a37b..072196605 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -42,7 +42,7 @@ + Source="{Binding Image.Value}" /> diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index a4fe2ede4..a64836285 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -18,6 +18,7 @@ namespace Flow.Launcher.ViewModel if (result != null) { Result = result; + Image = new Lazy(() => SetImage); } Settings = settings; @@ -36,8 +37,10 @@ namespace Flow.Launcher.ViewModel public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip) ? Result.SubTitle : Result.SubTitleToolTip; + + public Lazy Image { get; set; } - public ImageSource Image + private ImageSource SetImage { get { @@ -75,6 +78,7 @@ namespace Flow.Launcher.ViewModel } } + public override int GetHashCode() { return Result.GetHashCode(); From 52887aacf04e2f98de33e7caf21d2226924a0535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 11 Nov 2020 18:36:01 +0800 Subject: [PATCH 019/442] Batch process ui change with interval 50ms --- Flow.Launcher/ViewModel/MainViewModel.cs | 46 +++++++-- Flow.Launcher/ViewModel/ResultsForUpdate.cs | 2 +- Flow.Launcher/ViewModel/ResultsViewModel.cs | 107 ++++++++++---------- 3 files changed, 93 insertions(+), 62 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ac2cc79d5..0a1ef8d81 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -106,13 +106,25 @@ namespace Flow.Launcher.ViewModel { _resultsUpdateQueue = new BlockingCollection(); - Task.Run(() => + Task.Run(async () => { while (true) { - var resultToUpdate = _resultsUpdateQueue.Take(); - if (!resultToUpdate.Token.IsCancellationRequested) - UpdateResultView(resultToUpdate.Results, resultToUpdate.Metadata, resultToUpdate.Query); + List queue = new List() { _resultsUpdateQueue.Take() }; + await Task.Delay(50); + + while (_resultsUpdateQueue.TryTake(out var resultsForUpdate)) + { + queue.Add(resultsForUpdate); + } + + //foreach (var update in queue) + //{ + // UpdateResultView(update.Results, update.Metadata, update.Query); + //} + + UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); + } }); } @@ -412,7 +424,7 @@ namespace Flow.Launcher.ViewModel if (query != null) { // handle the exclusiveness of plugin using action keyword - RemoveOldQueryResults(query); + // RemoveOldQueryResults(query); _lastQuery = query; Task.Delay(200, currentCancellationToken).ContinueWith(_ => @@ -443,7 +455,7 @@ namespace Flow.Launcher.ViewModel { // nothing to do here } - + // this should happen once after all queries are done so progress bar should continue // until the end of all querying @@ -459,6 +471,7 @@ namespace Flow.Launcher.ViewModel { Results.Clear(); Results.Visbility = Visibility.Collapsed; + } } @@ -683,6 +696,23 @@ namespace Flow.Launcher.ViewModel } } + public void UpdateResultView(IEnumerable resultsForUpdates) + { + foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) + { + if (_topMostRecord.IsTopMost(result)) + { + result.Score = int.MaxValue; + } + else + { + result.Score += _userSelectedRecord.GetSelectedCount(result) * 5; + } + } + + Results.AddResults(resultsForUpdates); + } + /// /// To avoid deadlock, this method should not called from main thread /// @@ -705,10 +735,6 @@ namespace Flow.Launcher.ViewModel Results.AddResults(list, metadata.ID); } - if (Results.Visbility != Visibility.Visible && list.Count > 0) - { - Results.Visbility = Visibility.Visible; - } } #endregion diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs index 29f626285..2257d35b0 100644 --- a/Flow.Launcher/ViewModel/ResultsForUpdate.cs +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -6,7 +6,7 @@ using System.Threading; namespace Flow.Launcher.ViewModel { - class ResultsForUpdate + public class ResultsForUpdate { public List Results { get; } diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index ac435c494..d06a390c5 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -115,19 +116,20 @@ namespace Flow.Launcher.ViewModel public void Clear() { - Results.Clear(); + Results.RemoveAll(); } public void RemoveResultsExcept(PluginMetadata metadata) { - Results.RemoveAll(r => r.Result.PluginID != metadata.ID); + //Results.RemoveAll(r => r.Result.PluginID != metadata.ID); } public void RemoveResultsFor(PluginMetadata metadata) { - Results.RemoveAll(r => r.Result.PluginID == metadata.ID); + //Results.RemoveAll(r => r.Result.PluginID == metadata.ID); } + /// /// To avoid deadlock, this method should not called from main thread /// @@ -138,16 +140,44 @@ namespace Flow.Launcher.ViewModel // update UI in one run, so it can avoid UI flickering Results.Update(newResults); - if (Results.Count > 0) + if (Visbility != Visibility.Visible && Results.Count > 0) { Margin = new Thickness { Top = 8 }; SelectedIndex = 0; + Visbility = Visibility.Visible; } else { Margin = new Thickness { Top = 0 }; + Visbility = Visibility.Collapsed; } } + /// + /// To avoid deadlock, this method should not called from main thread + /// + public void AddResults(IEnumerable resultsForUpdates) + { + var newResults = NewResults(resultsForUpdates); + lock (_collectionLock) + { + Results.Update(newResults); + } + + switch (Visbility) + { + case Visibility.Collapsed when Results.Count > 0: + Margin = new Thickness { Top = 8 }; + SelectedIndex = 0; + Visbility = Visibility.Visible; + break; + case Visibility.Visible when Results.Count == 0: + Margin = new Thickness { Top = 0 }; + Visbility = Visibility.Collapsed; + break; + + } + } + private List NewResults(List newRawResults, string resultId) { @@ -165,8 +195,25 @@ namespace Flow.Launcher.ViewModel .Take(MaxResults * 2) .ToList(); } + + private List NewResults(IEnumerable resultsForUpdates) + { + if (!resultsForUpdates.Any()) + return Results.ToList(); + + var results = Results as IEnumerable; + + return results.Where(r => !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID)) + .Concat(resultsForUpdates + .SelectMany(u => u.Results) + .Select(r => new ResultViewModel(r, _settings))) + .OrderByDescending(rv => rv.Result.Score) + .Take(MaxResults * 2) + .ToList(); + } #endregion + #region FormattedText Dependency Property public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached( "FormattedText", @@ -198,20 +245,12 @@ namespace Flow.Launcher.ViewModel } #endregion - public class ResultCollection : ObservableCollection + public class ResultCollection : ObservableCollection, INotifyCollectionChanged { - - public void RemoveAll(Predicate predicate) + public event NotifyCollectionChangedEventHandler CollectionChanged; + public void RemoveAll() { - CheckReentrancy(); - - for (int i = Count - 1; i >= 0; i--) - { - if (predicate(this[i])) - { - RemoveAt(i); - } - } + ClearItems(); } /// @@ -226,44 +265,10 @@ namespace Flow.Launcher.ViewModel { Add(item); } + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - - return; - int newCount = newItems.Count; - int oldCount = Items.Count; - int location = newCount > oldCount ? oldCount : newCount; - - for (int i = 0; i < location; i++) - { - 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) - { - this[i].Result.Score = newResult.Result.Score; - } - } - - - if (newCount >= oldCount) - { - for (int i = oldCount; i < newCount; i++) - { - Add(newItems[i]); - } - } - else - { - for (int i = oldCount - 1; i >= newCount; i--) - { - RemoveAt(i); - } - } } } } From ebddf19ba21beccd961c3ad245640053e50fda3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 11 Nov 2020 19:06:05 +0800 Subject: [PATCH 020/442] Fix not canceled token and adding comment Co-authored-by: bao-qian --- Flow.Launcher/ViewModel/MainViewModel.cs | 6 ++++-- Flow.Launcher/ViewModel/ResultsViewModel.cs | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 0a1ef8d81..f71674774 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -469,9 +469,9 @@ namespace Flow.Launcher.ViewModel } else { + _updateSource?.Cancel(); Results.Clear(); Results.Visbility = Visibility.Collapsed; - } } @@ -695,7 +695,9 @@ namespace Flow.Launcher.ViewModel _saved = true; } } - + /// + /// To avoid deadlock, this method should not called from main thread + /// public void UpdateResultView(IEnumerable resultsForUpdates) { foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d06a390c5..635b20ec6 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -174,7 +174,6 @@ namespace Flow.Launcher.ViewModel Margin = new Thickness { Top = 0 }; Visbility = Visibility.Collapsed; break; - } } @@ -247,7 +246,7 @@ namespace Flow.Launcher.ViewModel public class ResultCollection : ObservableCollection, INotifyCollectionChanged { - public event NotifyCollectionChangedEventHandler CollectionChanged; + public override event NotifyCollectionChangedEventHandler CollectionChanged; public void RemoveAll() { ClearItems(); @@ -259,12 +258,16 @@ namespace Flow.Launcher.ViewModel /// public void Update(List newItems) { + + ClearItems(); foreach (var item in newItems) { Add(item); } + + // wpf use directx / double buffered already, so just reset all won't cause ui flickering CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); return; From 1b76da175fa210e77dfc178e7007ea7aa2bf5721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 12 Nov 2020 15:37:04 +0800 Subject: [PATCH 021/442] Add remove previous query result back to keep different action word distinct --- Flow.Launcher/ViewModel/MainViewModel.cs | 8 ++++---- Flow.Launcher/ViewModel/ResultsViewModel.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f71674774..cd2913570 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -424,7 +424,7 @@ namespace Flow.Launcher.ViewModel if (query != null) { // handle the exclusiveness of plugin using action keyword - // RemoveOldQueryResults(query); + RemoveOldQueryResults(query); _lastQuery = query; Task.Delay(200, currentCancellationToken).ContinueWith(_ => @@ -483,18 +483,18 @@ namespace Flow.Launcher.ViewModel { if (!string.IsNullOrEmpty(keyword)) { - Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + Results.KeepResultsFor(PluginManager.NonGlobalPlugins[keyword].Metadata); } } else { if (string.IsNullOrEmpty(keyword)) { - Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); + Results.KeepResultsExcept(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); } else if (lastKeyword != keyword) { - Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + Results.KeepResultsFor(PluginManager.NonGlobalPlugins[keyword].Metadata); } } } diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 635b20ec6..3b48c2d32 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -119,14 +119,14 @@ namespace Flow.Launcher.ViewModel Results.RemoveAll(); } - public void RemoveResultsExcept(PluginMetadata metadata) + public void KeepResultsFor(PluginMetadata metadata) { - //Results.RemoveAll(r => r.Result.PluginID != metadata.ID); + Results.Update(Results.Where(r => r.Result.PluginID == metadata.ID).ToList()); } - public void RemoveResultsFor(PluginMetadata metadata) + public void KeepResultsExcept(PluginMetadata metadata) { - //Results.RemoveAll(r => r.Result.PluginID == metadata.ID); + Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList()); } From f51d2c57c75dd77fbf30e3c3a26922f906fed792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 12 Nov 2020 15:46:24 +0800 Subject: [PATCH 022/442] keep all result in result list view --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 3b48c2d32..038cfb4e3 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -191,7 +191,6 @@ namespace Flow.Launcher.ViewModel return results.Where(r => r.Result.PluginID != resultId) .Concat(newResults) .OrderByDescending(r => r.Result.Score) - .Take(MaxResults * 2) .ToList(); } @@ -207,7 +206,6 @@ namespace Flow.Launcher.ViewModel .SelectMany(u => u.Results) .Select(r => new ResultViewModel(r, _settings))) .OrderByDescending(rv => rv.Result.Score) - .Take(MaxResults * 2) .ToList(); } #endregion From 4b2abfc7082764325c9fcee407ef7a202cba84a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 13 Nov 2020 13:41:59 +0800 Subject: [PATCH 023/442] Reduce batch time to speed up view update change selected index update place to fix not first selection issue --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- Flow.Launcher/ViewModel/ResultsViewModel.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index cd2913570..48e621126 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -111,7 +111,7 @@ namespace Flow.Launcher.ViewModel while (true) { List queue = new List() { _resultsUpdateQueue.Take() }; - await Task.Delay(50); + await Task.Delay(30); while (_resultsUpdateQueue.TryTake(out var resultsForUpdate)) { diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 038cfb4e3..3e464afe1 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -158,6 +158,11 @@ namespace Flow.Launcher.ViewModel public void AddResults(IEnumerable resultsForUpdates) { var newResults = NewResults(resultsForUpdates); + + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf + // fix selected index flow + SelectedIndex = 0; + lock (_collectionLock) { Results.Update(newResults); @@ -167,7 +172,6 @@ namespace Flow.Launcher.ViewModel { case Visibility.Collapsed when Results.Count > 0: Margin = new Thickness { Top = 8 }; - SelectedIndex = 0; Visbility = Visibility.Visible; break; case Visibility.Visible when Results.Count == 0: @@ -175,6 +179,7 @@ namespace Flow.Launcher.ViewModel Visbility = Visibility.Collapsed; break; } + } From e625c192ee5b64acba3195a6f7e0b16f0d6f7800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 13 Nov 2020 19:49:11 +0800 Subject: [PATCH 024/442] remove unused code --- Flow.Launcher/ViewModel/MainViewModel.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 48e621126..c093495e3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -118,11 +118,6 @@ namespace Flow.Launcher.ViewModel queue.Add(resultsForUpdate); } - //foreach (var update in queue) - //{ - // UpdateResultView(update.Results, update.Metadata, update.Query); - //} - UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); } From e34a52c74e778d06e1e538f1cd585c73142bf84e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 12:04:10 +0800 Subject: [PATCH 025/442] Don't change selected index unless collection is not empty --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 3e464afe1..d62e0d0c3 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -137,8 +137,16 @@ namespace Flow.Launcher.ViewModel { var newResults = NewResults(newRawResults, resultId); - // update UI in one run, so it can avoid UI flickering - Results.Update(newResults); + lock (_collectionLock) + { + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf + // fix selected index flow + if (Results.Count > 0) + SelectedIndex = 0; + + // update UI in one run, so it can avoid UI flickering + Results.Update(newResults); + } if (Visbility != Visibility.Visible && Results.Count > 0) { @@ -159,12 +167,14 @@ namespace Flow.Launcher.ViewModel { var newResults = NewResults(resultsForUpdates); - // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf - // fix selected index flow - SelectedIndex = 0; - lock (_collectionLock) { + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf + // fix selected index flow + if (Results.Count > 0) + SelectedIndex = 0; + + Results.Update(newResults); } @@ -172,6 +182,7 @@ namespace Flow.Launcher.ViewModel { case Visibility.Collapsed when Results.Count > 0: Margin = new Thickness { Top = 8 }; + SelectedIndex = 0; Visbility = Visibility.Visible; break; case Visibility.Visible when Results.Count == 0: From a4147f52c93d8ecd5af31f1b7e8e5130ff26693d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 12:36:29 +0800 Subject: [PATCH 026/442] Block Notifychange unless all the element is added into the observablecollection This should improve the response speed. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 36 ++++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d62e0d0c3..6210dc54f 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -260,7 +260,30 @@ namespace Flow.Launcher.ViewModel public class ResultCollection : ObservableCollection, INotifyCollectionChanged { + private bool _suppressNotification = false; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (!_suppressNotification) + base.OnCollectionChanged(e); + } + public override event NotifyCollectionChangedEventHandler CollectionChanged; + + public void AddRange(IEnumerable Items) + { + _suppressNotification = true; + + foreach (var item in Items) + { + Add(item); + } + + + // wpf use directx / double buffered already, so just reset all won't cause ui flickering + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + return; + + } public void RemoveAll() { ClearItems(); @@ -272,20 +295,9 @@ namespace Flow.Launcher.ViewModel /// public void Update(List newItems) { - - ClearItems(); - foreach (var item in newItems) - { - Add(item); - } - - // wpf use directx / double buffered already, so just reset all won't cause ui flickering - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - - return; - + AddRange(newItems); } } } From dab8c5da957b3d3ad2b34f65ee6c0dcb35b106d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 12:36:29 +0800 Subject: [PATCH 027/442] Block Notifychange unless all the element is added into the observablecollection This should improve the response speed. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 37 ++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d62e0d0c3..4acf71edd 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -260,7 +260,31 @@ namespace Flow.Launcher.ViewModel public class ResultCollection : ObservableCollection, INotifyCollectionChanged { + private bool _suppressNotification = false; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (!_suppressNotification) + base.OnCollectionChanged(e); + } + public override event NotifyCollectionChangedEventHandler CollectionChanged; + + // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ + public void AddRange(IEnumerable Items) + { + _suppressNotification = true; + + foreach (var item in Items) + { + Add(item); + } + + + // wpf use directx / double buffered already, so just reset all won't cause ui flickering + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + return; + + } public void RemoveAll() { ClearItems(); @@ -272,20 +296,9 @@ namespace Flow.Launcher.ViewModel /// public void Update(List newItems) { - - ClearItems(); - foreach (var item in newItems) - { - Add(item); - } - - // wpf use directx / double buffered already, so just reset all won't cause ui flickering - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - - return; - + AddRange(newItems); } } } From 4cf143d435eeaabe61cfe64ade7e47bcbc352a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 15:07:56 +0800 Subject: [PATCH 028/442] Don't notify change until finishing up adding new result --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 4acf71edd..438b64d92 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -296,7 +296,7 @@ namespace Flow.Launcher.ViewModel /// public void Update(List newItems) { - ClearItems(); + Clear(); AddRange(newItems); } From 3076e501bfe629e10f96532d4a957187c7a3bb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 17:09:37 +0800 Subject: [PATCH 029/442] add null check when processing result update --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 438b64d92..5d6887a3f 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -217,10 +217,9 @@ namespace Flow.Launcher.ViewModel var results = Results as IEnumerable; - return results.Where(r => !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID)) - .Concat(resultsForUpdates - .SelectMany(u => u.Results) - .Select(r => new ResultViewModel(r, _settings))) + return results.Where(r => r != null && !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID)) + .Concat( + resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings))) .OrderByDescending(rv => rv.Result.Score) .ToList(); } From 7dc247333ce6fece537e99912f7210b4f453cc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 14 Nov 2020 17:20:48 +0800 Subject: [PATCH 030/442] remove check of empty of Results when change selected index --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 5d6887a3f..1db6ac624 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -171,9 +171,7 @@ namespace Flow.Launcher.ViewModel { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow - if (Results.Count > 0) - SelectedIndex = 0; - + SelectedIndex = 0; Results.Update(newResults); } From c89ee33c5b6c72fab2aee25f6584b83c776dec03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 20:39:02 +0800 Subject: [PATCH 031/442] Use Virtualizing StackPanel instead of GridView to greatly reduce the loading resources. --- Flow.Launcher/ResultListBox.xaml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 072196605..350ef018e 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -16,6 +16,7 @@ Style="{DynamicResource BaseListboxStyle}" Focusable="False" KeyboardNavigation.DirectionalNavigation="Cycle" SelectionMode="Single" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Standard" + ScrollViewer.IsDeferredScrollingEnabled="True" SelectionChanged="OnSelectionChanged" IsSynchronizedWithCurrentItem="True" PreviewMouseDown="ListBox_PreviewMouseDown"> @@ -29,26 +30,20 @@ - - + + - - - - - - + - + - + @@ -60,7 +55,7 @@ - + @@ -81,7 +76,7 @@ - + From dc982e277f3c7beff59a71675cde74853e7e0420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 20:45:00 +0800 Subject: [PATCH 032/442] Use a default image to list when the image hasn't been loaded in cache. Co-authored-by: Bao-Qian --- .../Image/ImageCache.cs | 13 +++++---- .../Image/ImageLoader.cs | 11 +++++++- Flow.Launcher/ViewModel/ResultViewModel.cs | 28 +++++++++++++------ Flow.Launcher/ViewModel/ResultsViewModel.cs | 3 +- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 80c6684f5..7482ac1cf 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -26,7 +26,7 @@ namespace Flow.Launcher.Infrastructure.Image private const int MaxCached = 50; public ConcurrentDictionary Data { get; private set; } = new ConcurrentDictionary(); private const int permissibleFactor = 2; - + public void Initialization(Dictionary usage) { foreach (var key in usage.Keys) @@ -44,14 +44,14 @@ namespace Flow.Launcher.Infrastructure.Image value.usage++; return value.imageSource; } - + return null; } set { Data.AddOrUpdate( - path, - new ImageUsage(0, value), + path, + new ImageUsage(0, value), (k, v) => { v.imageSource = value; @@ -67,7 +67,8 @@ namespace Flow.Launcher.Infrastructure.Image // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. - foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) + foreach (var key in Data.Where(x => x.Key != Constant.MissingImgIcon) + .OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) { if (!(key.Equals(Constant.ErrorIcon) || key.Equals(Constant.DefaultIcon))) { @@ -80,7 +81,7 @@ namespace Flow.Launcher.Infrastructure.Image public bool ContainsKey(string key) { - var contains = Data.ContainsKey(key); + var contains = Data.ContainsKey(key) && Data[key] != null; return contains; } diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index edfb88cbf..bc924926c 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -61,7 +61,7 @@ namespace Flow.Launcher.Infrastructure.Image { lock (_storage) { - _storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, y => y.usage)); + _storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, x => x.usage)); } } @@ -211,6 +211,15 @@ namespace Flow.Launcher.Infrastructure.Image option); } + public static bool CacheContainImage(string path) + { + return ImageCache.ContainsKey(path); + } + public static ImageSource LoadDefault(bool loadFullImage = false) + { + return LoadInternal(Constant.MissingImgIcon, loadFullImage).ImageSource; + } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index a64836285..76c2a3e48 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Threading; @@ -26,18 +27,18 @@ namespace Flow.Launcher.ViewModel public Settings Settings { get; private set; } - public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Hidden; + public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Hidden; public string OpenResultModifiers => Settings.OpenResultModifiers; public string ShowTitleToolTip => string.IsNullOrEmpty(Result.TitleToolTip) - ? Result.Title + ? Result.Title : Result.TitleToolTip; public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip) - ? Result.SubTitle + ? Result.SubTitle : Result.SubTitleToolTip; - + public Lazy Image { get; set; } private ImageSource SetImage @@ -57,9 +58,20 @@ namespace Flow.Launcher.ViewModel imagePath = Constant.MissingImgIcon; } } - - // will get here either when icoPath has value\icon delegate is null\when had exception in delegate - return ImageLoader.Load(imagePath); + + if (ImageLoader.CacheContainImage(imagePath)) + // will get here either when icoPath has value\icon delegate is null\when had exception in delegate + return ImageLoader.Load(imagePath); + else + { + Task.Run(() => + { + Image = new Lazy(() => ImageLoader.Load(imagePath)); + OnPropertyChanged(nameof(Image)); + }); + + return ImageLoader.LoadDefault(); + } } } @@ -78,7 +90,7 @@ namespace Flow.Launcher.ViewModel } } - + public override int GetHashCode() { return Result.GetHashCode(); diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 1db6ac624..eb40e75de 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -201,9 +201,10 @@ namespace Flow.Launcher.ViewModel var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); + return results.Where(r => r.Result.PluginID != resultId) - .Concat(newResults) + .Concat(results.Intersect(newResults).Union(newResults)) .OrderByDescending(r => r.Result.Score) .ToList(); } From 4dd4cdafbf24c7437785cf01407aa7f6ce4086c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 21:00:31 +0800 Subject: [PATCH 033/442] Revert "Use Virtualizing StackPanel instead of GridView to greatly reduce the loading resources." This reverts commit c89ee33c5b6c72fab2aee25f6584b83c776dec03. Revert it because it is not useful so just keep the original code. --- Flow.Launcher/ResultListBox.xaml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 350ef018e..072196605 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -16,7 +16,6 @@ Style="{DynamicResource BaseListboxStyle}" Focusable="False" KeyboardNavigation.DirectionalNavigation="Cycle" SelectionMode="Single" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Standard" - ScrollViewer.IsDeferredScrollingEnabled="True" SelectionChanged="OnSelectionChanged" IsSynchronizedWithCurrentItem="True" PreviewMouseDown="ListBox_PreviewMouseDown"> @@ -30,20 +29,26 @@ - - + + - + + + + + + - + - + @@ -55,7 +60,7 @@ - + @@ -76,7 +81,7 @@ - + From b1dd7b418136c791e0331ec7711421f285a2a54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 15 Nov 2020 21:03:39 +0800 Subject: [PATCH 034/442] Move Image Cache filtering earlier --- Flow.Launcher.Infrastructure/Image/ImageCache.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 7482ac1cf..cce0aaace 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -26,7 +26,7 @@ namespace Flow.Launcher.Infrastructure.Image private const int MaxCached = 50; public ConcurrentDictionary Data { get; private set; } = new ConcurrentDictionary(); private const int permissibleFactor = 2; - + public void Initialization(Dictionary usage) { foreach (var key in usage.Keys) @@ -65,15 +65,13 @@ namespace Flow.Launcher.Infrastructure.Image if (Data.Count > permissibleFactor * MaxCached) { // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. - - - foreach (var key in Data.Where(x => x.Key != Constant.MissingImgIcon) + foreach (var key in Data + .Where(x => x.Key != Constant.MissingImgIcon + && x.Key != Constant.ErrorIcon + && x.Key != Constant.DefaultIcon) .OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) { - if (!(key.Equals(Constant.ErrorIcon) || key.Equals(Constant.DefaultIcon))) - { - Data.TryRemove(key, out _); - } + Data.TryRemove(key, out _); } } } From 2161f27a88180c74ffc13046e8557a94b498a426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 08:32:11 +0800 Subject: [PATCH 035/442] fix unintended token argument pass --- Flow.Launcher/ViewModel/MainViewModel.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index c093495e3..21243a793 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -22,6 +22,7 @@ using Flow.Launcher.Storage; using System.Windows.Media; using Flow.Launcher.Infrastructure.Image; using System.Collections.Concurrent; +using Flow.Launcher.Infrastructure.Logger; namespace Flow.Launcher.ViewModel { @@ -442,7 +443,7 @@ namespace Flow.Launcher.ViewModel if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Add(new ResultsForUpdate(results, plugin.Metadata, query, _updateToken)); + _resultsUpdateQueue.Add(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); } }); } @@ -695,6 +696,20 @@ namespace Flow.Launcher.ViewModel /// public void UpdateResultView(IEnumerable resultsForUpdates) { + if (!resultsForUpdates.Any()) + return; + + try + { + var token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); + } + catch (Exception e) + { + Log.Debug("Illegal token information"); + } + + + foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) { if (_topMostRecord.IsTopMost(result)) From e7c02dac7201c347ae9aa03ce2d100b29f3e2ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 10:12:32 +0800 Subject: [PATCH 036/442] change SetImage to method instead of property to avoid unintended use --- Flow.Launcher/ViewModel/ResultViewModel.cs | 49 ++++++++++------------ 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 76c2a3e48..8ce35227f 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -19,7 +19,7 @@ namespace Flow.Launcher.ViewModel if (result != null) { Result = result; - Image = new Lazy(() => SetImage); + Image = new Lazy(SetImage); } Settings = settings; @@ -41,38 +41,35 @@ namespace Flow.Launcher.ViewModel public Lazy Image { get; set; } - private ImageSource SetImage + private ImageSource SetImage() { - get + var imagePath = Result.IcoPath; + if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) { - var imagePath = Result.IcoPath; - if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) + try { - try - { - return Result.Icon(); - } - catch (Exception e) - { - Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); - imagePath = Constant.MissingImgIcon; - } + return Result.Icon(); } - - if (ImageLoader.CacheContainImage(imagePath)) - // will get here either when icoPath has value\icon delegate is null\when had exception in delegate - return ImageLoader.Load(imagePath); - else + catch (Exception e) { - Task.Run(() => - { - Image = new Lazy(() => ImageLoader.Load(imagePath)); - OnPropertyChanged(nameof(Image)); - }); - - return ImageLoader.LoadDefault(); + Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); + imagePath = Constant.MissingImgIcon; } } + + if (ImageLoader.CacheContainImage(imagePath)) + // will get here either when icoPath has value\icon delegate is null\when had exception in delegate + return ImageLoader.Load(imagePath); + else + { + Task.Run(() => + { + Image = new Lazy(() => ImageLoader.Load(imagePath)); + OnPropertyChanged(nameof(Image)); + }); + + return ImageLoader.LoadDefault(); + } } public Result Result { get; } From 0a9bad3ffa25a2069f4e736e65b8a65a37ae44b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 12:46:39 +0800 Subject: [PATCH 037/442] Use Customized LazyAsync class to load image instead of the weird way of updating lazy class async --- .../Image/ImageLoader.cs | 12 ++-- Flow.Launcher/ViewModel/ResultViewModel.cs | 56 +++++++++++++++---- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index bc924926c..f61d4615a 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -18,6 +18,8 @@ namespace Flow.Launcher.Infrastructure.Image private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); private static IImageHashGenerator _hashGenerator; private static bool EnableImageHash = true; + public static ImageSource defaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); + private static readonly string[] ImageExtensions = { @@ -37,6 +39,7 @@ namespace Flow.Launcher.Infrastructure.Image var usage = LoadStorageToConcurrentDictionary(); + foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); @@ -213,13 +216,10 @@ namespace Flow.Launcher.Infrastructure.Image public static bool CacheContainImage(string path) { - return ImageCache.ContainsKey(path); - } - public static ImageSource LoadDefault(bool loadFullImage = false) - { - return LoadInternal(Constant.MissingImgIcon, loadFullImage).ImageSource; + return ImageCache.ContainsKey(path) && ImageCache[path] != null; } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); @@ -230,7 +230,7 @@ namespace Flow.Launcher.Infrastructure.Image string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; if (hash != null) { - + if (GuidToKey.TryGetValue(hash, out string key)) { // image already exists img = ImageCache[key] ?? img; diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 8ce35227f..511df5e59 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -8,18 +8,57 @@ using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; - +using Microsoft.FSharp.Core; namespace Flow.Launcher.ViewModel { public class ResultViewModel : BaseModel { + public class LazyAsync : Lazy> + { + private T defaultValue; + + + private readonly Action _updateCallback; + public T Value + { + get + { + if (!IsValueCreated) + { + base.Value.ContinueWith(_ => + { + _updateCallback(); + }); + return defaultValue; + } + else if (!base.Value.IsCompleted) + { + return defaultValue; + } + else return base.Value.Result; + } + } + public LazyAsync(Func> factory, T defaultValue, Action updateCallback) : base(factory) + { + if (defaultValue != null) + { + this.defaultValue = defaultValue; + } + _updateCallback = updateCallback; + + } + } + public ResultViewModel(Result result, Settings settings) { if (result != null) { Result = result; - Image = new Lazy(SetImage); + Image = new LazyAsync(SetImage, ImageLoader.defaultImage, () => + { + OnPropertyChanged(nameof(Image)); + }); } Settings = settings; @@ -39,9 +78,9 @@ namespace Flow.Launcher.ViewModel ? Result.SubTitle : Result.SubTitleToolTip; - public Lazy Image { get; set; } + public LazyAsync Image { get; set; } - private ImageSource SetImage() + private async Task SetImage() { var imagePath = Result.IcoPath; if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) @@ -62,14 +101,9 @@ namespace Flow.Launcher.ViewModel return ImageLoader.Load(imagePath); else { - Task.Run(() => - { - Image = new Lazy(() => ImageLoader.Load(imagePath)); - OnPropertyChanged(nameof(Image)); - }); - - return ImageLoader.LoadDefault(); + return await Task.Run(() => ImageLoader.Load(imagePath)); } + } public Result Result { get; } From d9ef68272b1b98d727d00096203ddd3fea1abfbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 12:58:53 +0800 Subject: [PATCH 038/442] change default image property name --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 2 +- Flow.Launcher/ViewModel/ResultViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index f61d4615a..fb2f426a0 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -18,7 +18,7 @@ namespace Flow.Launcher.Infrastructure.Image private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); private static IImageHashGenerator _hashGenerator; private static bool EnableImageHash = true; - public static ImageSource defaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); + public static ImageSource DefaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); private static readonly string[] ImageExtensions = diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 511df5e59..f4a51070f 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -55,7 +55,7 @@ namespace Flow.Launcher.ViewModel if (result != null) { Result = result; - Image = new LazyAsync(SetImage, ImageLoader.defaultImage, () => + Image = new LazyAsync(SetImage, ImageLoader.DefaultImage, () => { OnPropertyChanged(nameof(Image)); }); From 71cac9cb2335bdd409a08bd8a2c0b30459889a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 13:04:16 +0800 Subject: [PATCH 039/442] change the way of checking whether cache exist --- Flow.Launcher.Infrastructure/Image/ImageCache.cs | 3 +-- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index cce0aaace..c3d7e5cfa 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -79,8 +79,7 @@ namespace Flow.Launcher.Infrastructure.Image public bool ContainsKey(string key) { - var contains = Data.ContainsKey(key) && Data[key] != null; - return contains; + return Data.ContainsKey(key) && Data[key].imageSource != null; } public int CacheSize() diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index fb2f426a0..92cf9ac3f 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -216,7 +216,7 @@ namespace Flow.Launcher.Infrastructure.Image public static bool CacheContainImage(string path) { - return ImageCache.ContainsKey(path) && ImageCache[path] != null; + return ImageCache.ContainsKey(path); } From 23d9e253baed19786806f9236c5b74db7cdc56ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 16 Nov 2020 19:20:52 +0800 Subject: [PATCH 040/442] Add some cancellationtoken check --- Flow.Launcher/ViewModel/MainViewModel.cs | 15 +++------------ Flow.Launcher/ViewModel/ResultsViewModel.cs | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 21243a793..ccc351cd3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -698,16 +698,7 @@ namespace Flow.Launcher.ViewModel { if (!resultsForUpdates.Any()) return; - - try - { - var token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); - } - catch (Exception e) - { - Log.Debug("Illegal token information"); - } - + CancellationToken token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) @@ -722,10 +713,10 @@ namespace Flow.Launcher.ViewModel } } - Results.AddResults(resultsForUpdates); + Results.AddResults(resultsForUpdates, token); } - /// + /// U /// To avoid deadlock, this method should not called from main thread /// public void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index eb40e75de..cce532965 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; +using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -136,7 +137,6 @@ namespace Flow.Launcher.ViewModel public void AddResults(List newRawResults, string resultId) { var newResults = NewResults(newRawResults, resultId); - lock (_collectionLock) { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf @@ -163,9 +163,11 @@ namespace Flow.Launcher.ViewModel /// /// To avoid deadlock, this method should not called from main thread /// - public void AddResults(IEnumerable resultsForUpdates) + public void AddResults(IEnumerable resultsForUpdates, CancellationToken token) { var newResults = NewResults(resultsForUpdates); + if (token.IsCancellationRequested) + return; lock (_collectionLock) { @@ -173,7 +175,7 @@ namespace Flow.Launcher.ViewModel // fix selected index flow SelectedIndex = 0; - Results.Update(newResults); + Results.Update(newResults, token); } switch (Visbility) @@ -201,7 +203,7 @@ namespace Flow.Launcher.ViewModel var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); - + return results.Where(r => r.Result.PluginID != resultId) .Concat(results.Intersect(newResults).Union(newResults)) @@ -292,10 +294,17 @@ namespace Flow.Launcher.ViewModel /// Update the results collection with new results, try to keep identical results /// /// + public void Update(List newItems, CancellationToken token) + { + Clear(); + if (token.IsCancellationRequested) + return; + AddRange(newItems); + } + public void Update(List newItems) { Clear(); - AddRange(newItems); } } From 2b423d9d80b3c6ced13c78feeec7d867eb3e3b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 17 Nov 2020 16:46:36 +0800 Subject: [PATCH 041/442] Use single add notify for each element when the new Item is small or the collection hasn't been buffer a lot. This seems solve the startup stunt and potentially make render faster. (Need Testing) --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 47 ++++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index cce532965..3a4b07fd6 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; +using System.Configuration; using System.Linq; using System.Threading; using System.Windows; @@ -137,6 +139,7 @@ namespace Flow.Launcher.ViewModel public void AddResults(List newRawResults, string resultId) { var newResults = NewResults(newRawResults, resultId); + lock (_collectionLock) { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf @@ -258,30 +261,38 @@ namespace Flow.Launcher.ViewModel } #endregion - public class ResultCollection : ObservableCollection, INotifyCollectionChanged + public class ResultCollection : ObservableCollection { private bool _suppressNotification = false; + + private long editTime = 0; + + // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (!_suppressNotification) base.OnCollectionChanged(e); } - public override event NotifyCollectionChangedEventHandler CollectionChanged; - - // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ - public void AddRange(IEnumerable Items) + public void BulkAddRange(IEnumerable resultViews) { _suppressNotification = true; + foreach (var item in resultViews) + { + Add(item); + } + _suppressNotification = false; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + public void AddRange(IEnumerable Items) + { foreach (var item in Items) { Add(item); } - // wpf use directx / double buffered already, so just reset all won't cause ui flickering - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); return; } @@ -296,16 +307,32 @@ namespace Flow.Launcher.ViewModel /// public void Update(List newItems, CancellationToken token) { - Clear(); + if (token.IsCancellationRequested) return; - AddRange(newItems); + Update(newItems); } public void Update(List newItems) { + if (editTime == 0) + { + AddRange(newItems); + editTime++; + return; + } + else if (editTime < 15 || newItems.Count < 50) + { + ClearItems(); + AddRange(newItems); + editTime++; + return; + } Clear(); - AddRange(newItems); + + BulkAddRange(newItems); + editTime++; + } } } From 6863b412e3060f3ff1d69a08c40891fb5d5182ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 20 Nov 2020 22:45:33 +0800 Subject: [PATCH 042/442] Use TPL's BufferBlock instead of BlockingCollection, which will block the current Thread. This should make the Task.Run works better. --- Flow.Launcher/ViewModel/MainViewModel.cs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ccc351cd3..b6bb6703a 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -23,6 +23,7 @@ using System.Windows.Media; using Flow.Launcher.Infrastructure.Image; using System.Collections.Concurrent; using Flow.Launcher.Infrastructure.Logger; +using System.Threading.Tasks.Dataflow; namespace Flow.Launcher.ViewModel { @@ -49,7 +50,7 @@ namespace Flow.Launcher.ViewModel private bool _saved; private readonly Internationalization _translator = InternationalizationManager.Instance; - private BlockingCollection _resultsUpdateQueue; + private BufferBlock _resultsUpdateQueue; #endregion @@ -94,10 +95,10 @@ namespace Flow.Launcher.ViewModel var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { - Task.Run(() => + Task.Run(async () => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - _resultsUpdateQueue.Add(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); + await _resultsUpdateQueue.SendAsync(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); }, _updateToken); }; } @@ -105,20 +106,14 @@ namespace Flow.Launcher.ViewModel private void RegisterResultUpdate() { - _resultsUpdateQueue = new BlockingCollection(); + _resultsUpdateQueue = new BufferBlock(); Task.Run(async () => { - while (true) + while (await _resultsUpdateQueue.OutputAvailableAsync()) { - List queue = new List() { _resultsUpdateQueue.Take() }; await Task.Delay(30); - - while (_resultsUpdateQueue.TryTake(out var resultsForUpdate)) - { - queue.Add(resultsForUpdate); - } - + _resultsUpdateQueue.TryReceiveAll(out var queue); UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); } @@ -443,7 +438,7 @@ namespace Flow.Launcher.ViewModel if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Add(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); } }); } From ea5e9a0fae49a9e9bdeee4791509764ffe07452e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 20 Nov 2020 23:05:12 +0800 Subject: [PATCH 043/442] Use New keyword for customized lazyAsync Value property --- Flow.Launcher/ViewModel/ResultViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index f4a51070f..0909b342f 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -20,7 +20,7 @@ namespace Flow.Launcher.ViewModel private readonly Action _updateCallback; - public T Value + public new T Value { get { From 4fb884cf570da817d6286883050308b31313f505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 20 Nov 2020 23:21:04 +0800 Subject: [PATCH 044/442] Wait 50 ms for query staying same when updating view. --- Flow.Launcher/ViewModel/MainViewModel.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index b6bb6703a..6f67ffcf5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -229,6 +229,7 @@ namespace Flow.Launcher.ViewModel public ResultsViewModel Results { get; private set; } public ResultsViewModel ContextMenu { get; private set; } public ResultsViewModel History { get; private set; } + private string _lastQueryText; private string _queryText; public string QueryText @@ -427,8 +428,14 @@ namespace Flow.Launcher.ViewModel }, currentCancellationToken); var plugins = PluginManager.ValidPluginsForQuery(query); - Task.Run(() => + Task.Run(async () => { + // Wait 50 millisecond for query change + // if query stay the same, update the view + await Task.Delay(50); + if (!(_lastQuery.RawQuery == QueryText)) + return; + // so looping will stop once it was cancelled var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; try From f11b302c37381c65ed50aa95ac07dc54b383ce7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 21 Nov 2020 12:01:57 +0800 Subject: [PATCH 045/442] slighly reduce the delay time --- Flow.Launcher/ViewModel/MainViewModel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 6f67ffcf5..fe1062cbb 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -112,7 +112,7 @@ namespace Flow.Launcher.ViewModel { while (await _resultsUpdateQueue.OutputAvailableAsync()) { - await Task.Delay(30); + await Task.Delay(20); _resultsUpdateQueue.TryReceiveAll(out var queue); UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); @@ -430,9 +430,9 @@ namespace Flow.Launcher.ViewModel var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => { - // Wait 50 millisecond for query change + // Wait 45 millisecond for query change // if query stay the same, update the view - await Task.Delay(50); + await Task.Delay(45); if (!(_lastQuery.RawQuery == QueryText)) return; From 34dc0d0220e73c9ebe59a6efc1cf232828f68bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 21 Nov 2020 16:42:15 +0800 Subject: [PATCH 046/442] move progressbar task position and change the way of comparison --- Flow.Launcher/ViewModel/MainViewModel.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index fe1062cbb..4e0285d9e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -419,13 +419,6 @@ namespace Flow.Launcher.ViewModel RemoveOldQueryResults(query); _lastQuery = query; - 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; - } - }, currentCancellationToken); var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => @@ -433,9 +426,18 @@ namespace Flow.Launcher.ViewModel // Wait 45 millisecond for query change // if query stay the same, update the view await Task.Delay(45); - if (!(_lastQuery.RawQuery == QueryText)) + if (!(_lastQuery.Search == query.Search)) return; + _ = 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; + } + }, currentCancellationToken); + // so looping will stop once it was cancelled var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; try From 9bc58fe9dacfd5b78e2c75be53e3ad2bd72dfe21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 22 Nov 2020 18:24:27 +0800 Subject: [PATCH 047/442] Only stop querying for global query --- Flow.Launcher/ViewModel/MainViewModel.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 4e0285d9e..1f8e72b8c 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -79,9 +79,10 @@ namespace Flow.Launcher.ViewModel _selectedResults = Results; InitializeKeyCommands(); - RegisterResultsUpdatedEvent(); RegisterResultUpdate(); + RegisterResultsUpdatedEvent(); + SetHotkey(_settings.Hotkey, OnHotkey); SetCustomPluginHotkey(); @@ -423,11 +424,14 @@ namespace Flow.Launcher.ViewModel var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => { - // Wait 45 millisecond for query change - // if query stay the same, update the view - await Task.Delay(45); - if (!(_lastQuery.Search == query.Search)) - return; + if (plugins.Count > 1) + { + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45); + if (!(_lastQuery.Search == query.Search)) + return; + } _ = Task.Delay(200, currentCancellationToken).ContinueWith(_ => { From 81a02c6e64ed67825b547d64393b83911cefdf08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 24 Nov 2020 17:38:24 +0800 Subject: [PATCH 048/442] Don't add result to queue unless the query match (plugin event driven update) Remove the Task.Run because updating result metadata and adding result to queue should not be time wasted. --- Flow.Launcher/ViewModel/MainViewModel.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 3d3792c54..f398df92b 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -96,11 +96,9 @@ namespace Flow.Launcher.ViewModel var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { - Task.Run(async () => - { - PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - await _resultsUpdateQueue.SendAsync(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); - }, _updateToken); + PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); + if (e.Query.Search == _lastQuery.Search) + _resultsUpdateQueue.Post(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); }; } } @@ -428,13 +426,13 @@ namespace Flow.Launcher.ViewModel { // Wait 45 millisecond for query change in global query // if query changes, return so that it won't be calculated - await Task.Delay(45); + await Task.Delay(45, currentCancellationToken); if (!(_lastQuery.Search == query.Search)) return; } _ = 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) { From c1107494344977f51865dbfba96ec5dd13b4ee5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 24 Nov 2020 17:41:57 +0800 Subject: [PATCH 049/442] Add more cancellation --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f398df92b..fdae8ecd4 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -449,7 +449,8 @@ namespace Flow.Launcher.ViewModel if (!plugin.Metadata.Disabled) { var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); + if (!currentCancellationToken.IsCancellationRequested) + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)); } }); } From 69e11e2861622d85b957e983e6c736ff745eb5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 24 Nov 2020 18:59:16 +0800 Subject: [PATCH 050/442] Use CancellationToken.IsCancellationRequested wherever is possible to use, check equality of query whererever token is unaccessiable. Dispose CancellationTokenSourse manually --- Flow.Launcher/ViewModel/MainViewModel.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index fdae8ecd4..9316cd62e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -404,6 +404,8 @@ namespace Flow.Launcher.ViewModel if (!string.IsNullOrEmpty(QueryText)) { _updateSource?.Cancel(); + _updateSource?.Dispose(); + var currentUpdateSource = new CancellationTokenSource(); _updateSource = currentUpdateSource; var currentCancellationToken = _updateSource.Token; @@ -427,14 +429,14 @@ namespace Flow.Launcher.ViewModel // Wait 45 millisecond for query change in global query // if query changes, return so that it won't be calculated await Task.Delay(45, currentCancellationToken); - if (!(_lastQuery.Search == query.Search)) + if (currentCancellationToken.IsCancellationRequested) return; } _ = 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) + if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) { ProgressBarVisibility = Visibility.Visible; } @@ -463,7 +465,7 @@ namespace Flow.Launcher.ViewModel // this should happen once after all queries are done so progress bar should continue // until the end of all querying _isQueryRunning = false; - if (currentUpdateSource == _updateSource) + if (!currentCancellationToken.IsCancellationRequested) { // update to hidden if this is still the current query ProgressBarVisibility = Visibility.Hidden; } From 1c6769cbcc78b2988ded5f7b22e60a1590d1b382 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 27 Nov 2020 07:43:03 +1100 Subject: [PATCH 051/442] fix merge --- Flow.Launcher/ViewModel/ResultViewModel.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 6805f31aa..3a1b7fee4 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -100,17 +100,10 @@ namespace Flow.Launcher.ViewModel } if (ImageLoader.CacheContainImage(imagePath)) - { // will get here either when icoPath has value\icon delegate is null\when had exception in delegate return ImageLoader.Load(imagePath); - else - { - return await Task.Run(() => ImageLoader.Load(imagePath)); - } - else - { - return await Task.Run(() => ImageLoader.Load(imagePath)); - } + + return await Task.Run(() => ImageLoader.Load(imagePath)); } public Result Result { get; } From 05aa7062d7104b6d2bbb900ad26607f5f5db8a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 27 Nov 2020 08:46:59 +0800 Subject: [PATCH 052/442] Change ErrorIcon to MissingImgIcon in ImageCache check since we don't use ErrorIcon anymore --- Flow.Launcher.Infrastructure/Image/ImageCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index fdbdddb2d..b8da3b030 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -66,7 +66,7 @@ namespace Flow.Launcher.Infrastructure.Image { // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. foreach (var key in Data - .Where(x => x.Key != Constant.ErrorIcon + .Where(x => x.Key != Constant.MissingImgIcon && x.Key != Constant.DefaultIcon) .OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) { From 813e9b5439592fb3057bf5ff8af76d50d2df8f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 28 Nov 2020 13:25:49 +0800 Subject: [PATCH 053/442] fix selected item doesn't always be the first item issue. Method: Not only update selected index, but also update selected item when adding results. --- Flow.Launcher/ResultListBox.xaml | 2 +- Flow.Launcher/ViewModel/ResultsViewModel.cs | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 072196605..2f9d06d81 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -9,7 +9,7 @@ d:DataContext="{d:DesignInstance vm:ResultsViewModel}" MaxHeight="{Binding MaxHeight}" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" - SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}" + SelectedItem="{Binding SelectedItem, Mode=TwoWay}" HorizontalContentAlignment="Stretch" ItemsSource="{Binding Results}" Margin="{Binding Margin}" Visibility="{Binding Visbility}" diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 3a4b07fd6..384647395 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -144,11 +144,10 @@ namespace Flow.Launcher.ViewModel { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow - if (Results.Count > 0) - SelectedIndex = 0; // update UI in one run, so it can avoid UI flickering Results.Update(newResults); + SelectedItem = newResults[0]; } if (Visbility != Visibility.Visible && Results.Count > 0) @@ -176,9 +175,11 @@ namespace Flow.Launcher.ViewModel { // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow - SelectedIndex = 0; Results.Update(newResults, token); + SelectedItem = newResults[0]; + + } switch (Visbility) @@ -321,13 +322,6 @@ namespace Flow.Launcher.ViewModel editTime++; return; } - else if (editTime < 15 || newItems.Count < 50) - { - ClearItems(); - AddRange(newItems); - editTime++; - return; - } Clear(); BulkAddRange(newItems); From a640eadf1b99fd53787a32c5b4921cac38870914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 29 Nov 2020 14:56:08 +0800 Subject: [PATCH 054/442] Use List instead of ObservableCollection as base class for resultcollection for better customized control of event --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 62 +++++++++++---------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 384647395..6d476ec63 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -12,6 +12,7 @@ using System.Windows.Data; using System.Windows.Documents; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Microsoft.FSharp.Control; namespace Flow.Launcher.ViewModel { @@ -177,6 +178,8 @@ namespace Flow.Launcher.ViewModel // fix selected index flow Results.Update(newResults, token); + if (token.IsCancellationRequested) + return; SelectedItem = newResults[0]; @@ -262,35 +265,33 @@ namespace Flow.Launcher.ViewModel } #endregion - public class ResultCollection : ObservableCollection + public class ResultCollection : List, INotifyCollectionChanged { - private bool _suppressNotification = false; + + public event NotifyCollectionChangedEventHandler CollectionChanged; private long editTime = 0; + // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (!_suppressNotification) - base.OnCollectionChanged(e); + if (CollectionChanged != null && CollectionChanged.GetInvocationList().Length == 1) + CollectionChanged.Invoke(this, e); } - public void BulkAddRange(IEnumerable resultViews) + public void BulkAddRange(IEnumerable resultViews, CancellationToken? token) { - _suppressNotification = true; - foreach (var item in resultViews) - { - Add(item); - } - _suppressNotification = false; + AddRange(resultViews); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - public void AddRange(IEnumerable Items) + public void AddAll(IEnumerable Items, CancellationToken? token) { foreach (var item in Items) { + if (token?.IsCancellationRequested ?? false) return; Add(item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } // wpf use directx / double buffered already, so just reset all won't cause ui flickering @@ -299,33 +300,34 @@ namespace Flow.Launcher.ViewModel } public void RemoveAll() { - ClearItems(); + Clear(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// /// Update the results collection with new results, try to keep identical results /// - /// - public void Update(List newItems, CancellationToken token) + /// New Items to add into the list view + /// Cancellation Token + public void Update(List newItems, CancellationToken? token = null) { - - if (token.IsCancellationRequested) + if (newItems.Count == 0 || (token?.IsCancellationRequested ?? false)) return; - Update(newItems); - } + - public void Update(List newItems) - { - if (editTime == 0) + if (editTime < 5 || newItems.Count < 30) { - AddRange(newItems); + if (Count > 0) RemoveAll(); + if (token?.IsCancellationRequested ?? false) return; + AddAll(newItems, token); + editTime++; + } + else + { + Clear(); + BulkAddRange(newItems, token); editTime++; - return; } - Clear(); - - BulkAddRange(newItems); - editTime++; } } From 1ddf83fc86d4b9a54ed93fdc8f5a359f8fb0e0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 30 Nov 2020 11:33:31 +0800 Subject: [PATCH 055/442] Update to DotNet5 --- Flow.Launcher.Core/Flow.Launcher.Core.csproj | 4 ++-- .../Flow.Launcher.Infrastructure.csproj | 5 ++--- Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj | 6 +++--- Flow.Launcher.Test/Flow.Launcher.Test.csproj | 3 +-- Flow.Launcher/Flow.Launcher.csproj | 4 ++-- .../NetCore3.1-SelfContained.pubxml | 2 +- .../Flow.Launcher.Plugin.BrowserBookmark.csproj | 14 ++++---------- .../Flow.Launcher.Plugin.Calculator.csproj | 4 ++-- .../Flow.Launcher.Plugin.Color.csproj | 4 ++-- .../Flow.Launcher.Plugin.ControlPanel.csproj | 4 ++-- .../Flow.Launcher.Plugin.Explorer.csproj | 4 ++-- .../Flow.Launcher.Plugin.PluginIndicator.csproj | 4 ++-- .../Flow.Launcher.Plugin.PluginManagement.csproj | 4 ++-- .../Flow.Launcher.Plugin.ProcessKiller.csproj | 2 +- .../Flow.Launcher.Plugin.Program.csproj | 5 ++--- .../Flow.Launcher.Plugin.Shell.csproj | 5 ++--- .../Flow.Launcher.Plugin.Sys.csproj | 4 ++-- .../Flow.Launcher.Plugin.Url.csproj | 4 ++-- .../Flow.Launcher.Plugin.WebSearch.csproj | 14 +++----------- Scripts/flowlauncher.nuspec | 2 +- global.json | 2 +- 21 files changed, 41 insertions(+), 59 deletions(-) diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 9f146a457..370f2015b 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -1,7 +1,7 @@ - + - netcoreapp3.1 + net5.0-windows true true Library diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 28d4c0e1f..48d9486cf 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -1,7 +1,6 @@ - - + - netcoreapp3.1 + net5.0-windows {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3} Library true diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 0f6450d18..38ae6898f 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -1,7 +1,7 @@ - - + + - netcoreapp3.1 + net5.0-windows {8451ECDD-2EA4-4966-BB0A-7BBC40138E80} true Library diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj index e970c47b9..fb972d7d4 100644 --- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj +++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0-windows10.0.19041.0 {FF742965-9A80-41A5-B042-D6C7D3A21708} Library Properties @@ -54,7 +54,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - \ No newline at end of file diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 8548ba39e..f959f3aa1 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -1,8 +1,8 @@ - + WinExe - netcoreapp3.1 + net5.0-windows10.0.19041.0 true true Flow.Launcher.App diff --git a/Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml b/Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml index 2794a0cea..cad0fd462 100644 --- a/Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml +++ b/Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml @@ -7,7 +7,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release Any CPU - netcoreapp3.1 + net5.0-windows ..\Output\Release\ win-x64 true diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index 85b745a6b..ee78f78c4 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -1,8 +1,9 @@ - + Library - netcoreapp3.1 + net5.0-windows + true {9B130CC5-14FB-41FF-B310-0A95B6894C37} Properties Flow.Launcher.Plugin.BrowserBookmark @@ -68,14 +69,7 @@ PreserveNewest - - - - MSBuild:Compile - Designer - - - + diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index 9e1fefdb3..bc637be54 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows {59BD9891-3837-438A-958D-ADC7F91F6F7E} Properties Flow.Launcher.Plugin.Caculator diff --git a/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj b/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj index c7fe8271a..18f81e3ff 100644 --- a/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj +++ b/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows {F35190AA-4758-4D9E-A193-E3BDF6AD3567} Properties Flow.Launcher.Plugin.Color diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj index 699737634..be62c31cd 100644 --- a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj +++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows {1EE20B48-82FB-48A2-8086-675D6DDAB4F0} Properties Flow.Launcher.Plugin.ControlPanel diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index a1a08843a..99e9c784b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows true true true diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj index e6bfa7aa3..a904a9272 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows {FDED22C8-B637-42E8-824A-63B5B6E05A3A} Properties Flow.Launcher.Plugin.PluginIndicator diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj b/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj index 08e89d861..4a5df0b3a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows {049490F0-ECD2-4148-9B39-2135EC346EBE} Properties Flow.Launcher.Plugin.PluginManagement diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index cf9c96294..1aef8f58e 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -2,7 +2,7 @@ Library - netcoreapp3.1 + net5.0-windows Flow.Launcher.Plugin.ProcessKiller Flow.Launcher.Plugin.ProcessKiller Flow-Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index 3802297c7..61d8b7b6c 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows10.0.19041.0 {FDB3555B-58EF-4AE6-B5F1-904719637AB4} Properties Flow.Launcher.Plugin.Program @@ -109,7 +109,6 @@ - diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index 178d95010..12ee6833e 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -1,8 +1,7 @@ - - + Library - netcoreapp3.1 + net5.0-windows {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} Properties Flow.Launcher.Plugin.Shell diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index bdab40457..6618ca775 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows {0B9DE348-9361-4940-ADB6-F5953BFFCCEC} Properties Flow.Launcher.Plugin.Sys diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index 7d802d815..d5a056825 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -1,8 +1,8 @@ - + Library - netcoreapp3.1 + net5.0-windows {A3DCCBCA-ACC1-421D-B16E-210896234C26} true Properties diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj index 431ca9ce8..ed2c5107c 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj @@ -1,8 +1,8 @@ - - + Library - netcoreapp3.1 + net5.0-windows + true {403B57F2-1856-4FC7-8A24-36AB346B763E} Properties Flow.Launcher.Plugin.WebSearch @@ -119,14 +119,6 @@ Designer PreserveNewest - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - diff --git a/Scripts/flowlauncher.nuspec b/Scripts/flowlauncher.nuspec index 6c5deb86b..f1f58f2d1 100644 --- a/Scripts/flowlauncher.nuspec +++ b/Scripts/flowlauncher.nuspec @@ -11,6 +11,6 @@ Flow Launcher - a launcher for windows - + diff --git a/global.json b/global.json index c3efffd40..2ad4d9100 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "3.1.201", + "version": "5.0.100", "rollForward": "latestFeature" } } \ No newline at end of file From 3607bfde078af0f08dc4666fa1adede255e26f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 1 Dec 2020 10:37:05 +0800 Subject: [PATCH 056/442] Change name for publish file --- .../NetCore3.1-SelfContained.pubxml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml diff --git a/Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml b/Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml deleted file mode 100644 index cad0fd462..000000000 --- a/Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - FileSystem - Release - Any CPU - net5.0-windows - ..\Output\Release\ - win-x64 - true - False - False - False - - \ No newline at end of file From e973e1d0bb6b97c4e86fcac380b536bfc9464fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 2 Dec 2020 13:26:05 +0800 Subject: [PATCH 057/442] Update ModernWPF so that the app can run --- Flow.Launcher/Flow.Launcher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index f959f3aa1..2f58abab0 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -72,7 +72,7 @@ - + all From 19c7446552f6242cc43981948ea5eb24783f5dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 2 Dec 2020 13:56:50 +0800 Subject: [PATCH 058/442] Add Lock to the place we directly update the ResultCollection --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 6d476ec63..d373b0fda 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -120,17 +120,20 @@ namespace Flow.Launcher.ViewModel public void Clear() { - Results.RemoveAll(); + lock (_collectionLock) + Results.RemoveAll(); } public void KeepResultsFor(PluginMetadata metadata) { - Results.Update(Results.Where(r => r.Result.PluginID == metadata.ID).ToList()); + lock (_collectionLock) + Results.Update(Results.Where(r => r.Result.PluginID == metadata.ID).ToList()); } public void KeepResultsExcept(PluginMetadata metadata) { - Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList()); + lock (_collectionLock) + Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList()); } @@ -148,7 +151,8 @@ namespace Flow.Launcher.ViewModel // update UI in one run, so it can avoid UI flickering Results.Update(newResults); - SelectedItem = newResults[0]; + if (newResults.Any()) + SelectedItem = newResults[0]; } if (Visbility != Visibility.Visible && Results.Count > 0) @@ -304,6 +308,9 @@ namespace Flow.Launcher.ViewModel OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } + + + /// /// Update the results collection with new results, try to keep identical results /// @@ -311,9 +318,9 @@ namespace Flow.Launcher.ViewModel /// Cancellation Token public void Update(List newItems, CancellationToken? token = null) { - if (newItems.Count == 0 || (token?.IsCancellationRequested ?? false)) + if (token?.IsCancellationRequested ?? false) return; - + if (editTime < 5 || newItems.Count < 30) { From 98e6a1ab8a203201d08de9a8f1bc5273c81a7789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 2 Dec 2020 20:57:23 +0800 Subject: [PATCH 059/442] Revert using List instead of observablecollection --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 66 ++++++++++----------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d373b0fda..5a9043c23 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -12,7 +12,6 @@ using System.Windows.Data; using System.Windows.Documents; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Microsoft.FSharp.Control; namespace Flow.Launcher.ViewModel { @@ -182,8 +181,6 @@ namespace Flow.Launcher.ViewModel // fix selected index flow Results.Update(newResults, token); - if (token.IsCancellationRequested) - return; SelectedItem = newResults[0]; @@ -269,33 +266,35 @@ namespace Flow.Launcher.ViewModel } #endregion - public class ResultCollection : List, INotifyCollectionChanged + public class ResultCollection : ObservableCollection { - - public event NotifyCollectionChangedEventHandler CollectionChanged; + private bool _suppressNotification = false; private long editTime = 0; - // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ - protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (CollectionChanged != null && CollectionChanged.GetInvocationList().Length == 1) - CollectionChanged.Invoke(this, e); + if (!_suppressNotification) + base.OnCollectionChanged(e); } - public void BulkAddRange(IEnumerable resultViews, CancellationToken? token) + public void BulkAddRange(IEnumerable resultViews) { - AddRange(resultViews); + _suppressNotification = true; + foreach (var item in resultViews) + { + Add(item); + } + _suppressNotification = false; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } - public void AddAll(IEnumerable Items, CancellationToken? token) + public void AddRange(IEnumerable Items) { foreach (var item in Items) { - if (token?.IsCancellationRequested ?? false) return; Add(item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } // wpf use directx / double buffered already, so just reset all won't cause ui flickering @@ -304,8 +303,7 @@ namespace Flow.Launcher.ViewModel } public void RemoveAll() { - Clear(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ClearItems(); } @@ -314,27 +312,27 @@ namespace Flow.Launcher.ViewModel /// /// Update the results collection with new results, try to keep identical results /// - /// New Items to add into the list view - /// Cancellation Token - public void Update(List newItems, CancellationToken? token = null) + /// + public void Update(List newItems, CancellationToken token) { - if (token?.IsCancellationRequested ?? false) + + if (token.IsCancellationRequested) return; + Update(newItems); + } + public void Update(List newItems) + { + if (editTime == 0) + { + AddRange(newItems); + editTime++; + return; + } + Clear(); - if (editTime < 5 || newItems.Count < 30) - { - if (Count > 0) RemoveAll(); - if (token?.IsCancellationRequested ?? false) return; - AddAll(newItems, token); - editTime++; - } - else - { - Clear(); - BulkAddRange(newItems, token); - editTime++; - } + BulkAddRange(newItems); + editTime++; } } From e7dd8676ec247f3a670867f4cb75a4263228cb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 2 Dec 2020 23:37:23 +0800 Subject: [PATCH 060/442] Change some code and check out what cause those error --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 65 ++++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 5a9043c23..e05e3c153 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -141,17 +141,18 @@ namespace Flow.Launcher.ViewModel /// public void AddResults(List newRawResults, string resultId) { - var newResults = NewResults(newRawResults, resultId); lock (_collectionLock) { + var newResults = NewResults(newRawResults, resultId); + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow // update UI in one run, so it can avoid UI flickering Results.Update(newResults); - if (newResults.Any()) - SelectedItem = newResults[0]; + if (Results.Any()) + SelectedItem = Results[0]; } if (Visbility != Visibility.Visible && Results.Count > 0) @@ -171,17 +172,20 @@ namespace Flow.Launcher.ViewModel /// public void AddResults(IEnumerable resultsForUpdates, CancellationToken token) { - var newResults = NewResults(resultsForUpdates); - if (token.IsCancellationRequested) - return; - lock (_collectionLock) { + var newResults = NewResults(resultsForUpdates); + if (token.IsCancellationRequested) + return; + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow Results.Update(newResults, token); - SelectedItem = newResults[0]; + if (token.IsCancellationRequested) + return; + if (Results.Any()) + SelectedItem = Results[0]; } @@ -268,33 +272,38 @@ namespace Flow.Launcher.ViewModel public class ResultCollection : ObservableCollection { - private bool _suppressNotification = false; private long editTime = 0; - // https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/ + private bool _suppressNotifying = false; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (!_suppressNotification) - base.OnCollectionChanged(e); + if (_suppressNotifying) + return; + base.OnCollectionChanged(e); } public void BulkAddRange(IEnumerable resultViews) { - _suppressNotification = true; + _suppressNotifying = true; + foreach (var item in resultViews) { Add(item); } - _suppressNotification = false; + _suppressNotifying = false; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - public void AddRange(IEnumerable Items) + public void AddRange(IEnumerable Items, CancellationToken? token) { foreach (var item in Items) { + if (token?.IsCancellationRequested ?? false) + return; + Add(item); + } // wpf use directx / double buffered already, so just reset all won't cause ui flickering @@ -313,27 +322,25 @@ namespace Flow.Launcher.ViewModel /// Update the results collection with new results, try to keep identical results /// /// - public void Update(List newItems, CancellationToken token) + public void Update(List newItems, CancellationToken? token = null) { - if (token.IsCancellationRequested) + if (token?.IsCancellationRequested ?? false) return; - Update(newItems); - } - public void Update(List newItems) - { - if (editTime == 0) + if (editTime < 5 || newItems.Count < 30) { - AddRange(newItems); + if (Count != 0) ClearItems(); + AddRange(newItems, token); editTime++; return; } - Clear(); - - BulkAddRange(newItems); - editTime++; - + else + { + Clear(); + BulkAddRange(newItems); + editTime++; + } } } } From 25bc2a70e3091de08f1a4a74050151c008c97bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 3 Dec 2020 10:33:19 +0800 Subject: [PATCH 061/442] Add timeout for waiting collectionchange event, although seems the deadlock isn't caused by this. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 51 ++++++++++++--------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index e05e3c153..40021690b 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -6,10 +6,12 @@ using System.ComponentModel; using System.Configuration; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; +using System.Windows.Forms; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -172,12 +174,11 @@ namespace Flow.Launcher.ViewModel /// public void AddResults(IEnumerable resultsForUpdates, CancellationToken token) { + var newResults = NewResults(resultsForUpdates); + if (token.IsCancellationRequested) + return; lock (_collectionLock) { - var newResults = NewResults(resultsForUpdates); - if (token.IsCancellationRequested) - return; - // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow @@ -186,8 +187,6 @@ namespace Flow.Launcher.ViewModel return; if (Results.Any()) SelectedItem = Results[0]; - - } switch (Visbility) @@ -277,38 +276,46 @@ namespace Flow.Launcher.ViewModel private bool _suppressNotifying = false; + private CancellationToken _token; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (_suppressNotifying) - return; - base.OnCollectionChanged(e); + if (!_suppressNotifying) + { + var notifyChangeTask = Task.Run(() => base.OnCollectionChanged(e)); + if (notifyChangeTask.Wait(300)) + return; + else + { + notifyChangeTask.Dispose(); + throw new TimeoutException(); + } + } } public void BulkAddRange(IEnumerable resultViews) { + // suppress notifying before adding all element _suppressNotifying = true; - foreach (var item in resultViews) { Add(item); } _suppressNotifying = false; + // manually update event + // wpf use directx / double buffered already, so just reset all won't cause ui flickering + if (_token.IsCancellationRequested) + return; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - public void AddRange(IEnumerable Items, CancellationToken? token) + public void AddRange(IEnumerable Items) { foreach (var item in Items) { - if (token?.IsCancellationRequested ?? false) + if (_token.IsCancellationRequested) return; - Add(item); - } - - // wpf use directx / double buffered already, so just reset all won't cause ui flickering - return; - } public void RemoveAll() { @@ -322,16 +329,16 @@ namespace Flow.Launcher.ViewModel /// Update the results collection with new results, try to keep identical results /// /// - public void Update(List newItems, CancellationToken? token = null) + public void Update(List newItems, CancellationToken token = default) { - - if (token?.IsCancellationRequested ?? false) + _token = token; + if (_token.IsCancellationRequested) return; if (editTime < 5 || newItems.Count < 30) { if (Count != 0) ClearItems(); - AddRange(newItems, token); + AddRange(newItems); editTime++; return; } From eb13a18d52d1e0659e8896d3bfc4dc3961fad445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 3 Dec 2020 15:38:00 +0800 Subject: [PATCH 062/442] Use task.run in addresult instead of CollectionChange event. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 45 +++++++++++++-------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 40021690b..924fd7ffc 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -150,11 +150,20 @@ namespace Flow.Launcher.ViewModel // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow + var updateTask = Task.Run(() => + { + // update UI in one run, so it can avoid UI flickering + + Results.Update(newResults); + if (Results.Any()) + SelectedItem = Results[0]; + }); + if (!updateTask.Wait(300)) + { + updateTask.Dispose(); + throw new TimeoutException("Update result use too much time."); + } - // update UI in one run, so it can avoid UI flickering - Results.Update(newResults); - if (Results.Any()) - SelectedItem = Results[0]; } if (Visbility != Visibility.Visible && Results.Count > 0) @@ -179,14 +188,23 @@ namespace Flow.Launcher.ViewModel return; lock (_collectionLock) { + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf // fix selected index flow + var updateTask = Task.Run(() => + { + // update UI in one run, so it can avoid UI flickering + + Results.Update(newResults, token); + if (Results.Any()) + SelectedItem = Results[0]; + }); + if (!updateTask.Wait(300)) + { + updateTask.Dispose(); + throw new TimeoutException("Update result use too much time."); + } - Results.Update(newResults, token); - if (token.IsCancellationRequested) - return; - if (Results.Any()) - SelectedItem = Results[0]; } switch (Visbility) @@ -282,14 +300,7 @@ namespace Flow.Launcher.ViewModel { if (!_suppressNotifying) { - var notifyChangeTask = Task.Run(() => base.OnCollectionChanged(e)); - if (notifyChangeTask.Wait(300)) - return; - else - { - notifyChangeTask.Dispose(); - throw new TimeoutException(); - } + base.OnCollectionChanged(e); } } From 01158fdc66bff9b8a64ff132e50807d4e317cc95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 21:53:34 +0800 Subject: [PATCH 063/442] Add error handling for the resultUpdateTask to make sure result update can continue when error throw. --- Flow.Launcher/ViewModel/MainViewModel.cs | 41 ++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 9316cd62e..dd790a98b 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -24,6 +24,7 @@ using Flow.Launcher.Infrastructure.Image; using System.Collections.Concurrent; using Flow.Launcher.Infrastructure.Logger; using System.Threading.Tasks.Dataflow; +using NLog; namespace Flow.Launcher.ViewModel { @@ -51,6 +52,7 @@ namespace Flow.Launcher.ViewModel private readonly Internationalization _translator = InternationalizationManager.Instance; private BufferBlock _resultsUpdateQueue; + private Task _resultsViewUpdateTask; #endregion @@ -80,7 +82,7 @@ namespace Flow.Launcher.ViewModel InitializeKeyCommands(); - RegisterResultUpdate(); + RegisterViewUpdate(); RegisterResultsUpdatedEvent(); @@ -103,20 +105,31 @@ namespace Flow.Launcher.ViewModel } } - private void RegisterResultUpdate() + private void RegisterViewUpdate() { _resultsUpdateQueue = new BufferBlock(); + _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); - Task.Run(async () => + + async Task updateAction() { while (await _resultsUpdateQueue.OutputAvailableAsync()) { await Task.Delay(20); _resultsUpdateQueue.TryReceiveAll(out var queue); UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); - } - }); + }; + + void continueAction(Task t) + { +#if DEBUG + throw t.Exception; +#else + Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}"); + _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continuationAction, TaskContinuationOptions.OnlyOnFaulted); +#endif + } } @@ -707,7 +720,23 @@ namespace Flow.Launcher.ViewModel { if (!resultsForUpdates.Any()) return; - CancellationToken token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); + CancellationToken token; + + try + { + token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); + } +#if DEBUG + catch + { + throw new ArgumentException("Unacceptable token"); + } +#else + catch + { + + } +#endif foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) From aa20d505fe5b42fe198461fa7f77513ec9e36138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 21:54:22 +0800 Subject: [PATCH 064/442] Revert creating a new task when adding result. --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 924fd7ffc..f12ea23e9 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -188,22 +188,12 @@ namespace Flow.Launcher.ViewModel return; lock (_collectionLock) { + // update UI in one run, so it can avoid UI flickering - // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf - // fix selected index flow - var updateTask = Task.Run(() => - { - // update UI in one run, so it can avoid UI flickering + Results.Update(newResults, token); + if (Results.Any()) + SelectedItem = Results[0]; - Results.Update(newResults, token); - if (Results.Any()) - SelectedItem = Results[0]; - }); - if (!updateTask.Wait(300)) - { - updateTask.Dispose(); - throw new TimeoutException("Update result use too much time."); - } } From 6267fa9a50f9454e9bdd85ed3dc4015f95822874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 22:18:51 +0800 Subject: [PATCH 065/442] fix typo. --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index dd790a98b..3e1580e38 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -127,7 +127,7 @@ namespace Flow.Launcher.ViewModel throw t.Exception; #else Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}"); - _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continuationAction, TaskContinuationOptions.OnlyOnFaulted); + _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); #endif } } From c3d5d4c0cecddc41a5fcdf7b18c1ccd7de87df84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 22:24:25 +0800 Subject: [PATCH 066/442] Don't update collection when both newItems and collection is empty --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index f12ea23e9..fc77327cc 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -333,7 +333,7 @@ namespace Flow.Launcher.ViewModel public void Update(List newItems, CancellationToken token = default) { _token = token; - if (_token.IsCancellationRequested) + if (Count == 0 && newItems.Count == 0 || _token.IsCancellationRequested) return; if (editTime < 5 || newItems.Count < 30) From 756c5bce3deb3af5df9e9554e930e20609083e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 22:30:14 +0800 Subject: [PATCH 067/442] fix using unintialized variable in release. --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 3e1580e38..1329421c3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -734,7 +734,7 @@ namespace Flow.Launcher.ViewModel #else catch { - + token = default; } #endif From 4ea40ab9aa7907b6648315cf42e4813b96df491d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 4 Dec 2020 22:46:23 +0800 Subject: [PATCH 068/442] Add the new publish profile file --- .../PublishProfiles/Net5-SelfContained.pubxml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml diff --git a/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml b/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml new file mode 100644 index 000000000..124792e3e --- /dev/null +++ b/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml @@ -0,0 +1,18 @@ + + + + + FileSystem + Release + Any CPU + net5.0-windows10.0.19041.0 + ..\Output\Release\ + win-x64 + true + False + False + False + + \ No newline at end of file From b8d3b42295d557784e5ab990a927e612f7fd4872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 5 Dec 2020 15:48:36 +0800 Subject: [PATCH 069/442] use singleordefault instead of single since sometims the queue may be empty but method doesn't return --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 1329421c3..25768a25e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -724,7 +724,8 @@ namespace Flow.Launcher.ViewModel try { - token = resultsForUpdates.Select(r => r.Token).Distinct().Single(); + // Don't know why sometimes even resultsForUpdates is empty, the method won't return; + token = resultsForUpdates.Select(r => r.Token).Distinct().SingleOrDefault(); } #if DEBUG catch From b67f5de4c57f5a7ce3a2bdb1d9cd283a8665e411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 5 Dec 2020 16:55:06 +0800 Subject: [PATCH 070/442] Port StringMatcher.FuzzySearch to IPublicAPI --- Flow.Launcher.Plugin/IPublicAPI.cs | 2 ++ Flow.Launcher/PublicAPIInstance.cs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index 681973905..b6f3f0680 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -88,5 +88,7 @@ namespace Flow.Launcher.Plugin /// if you want to hook something like Ctrl+R, you should use this event /// event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; + + public (List MatchedData, int Score, bool Success) MatchString(string query, string stringToCompare); } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 0cc5a0e5d..5d1ea7f24 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -132,6 +132,12 @@ namespace Flow.Launcher public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; + public (List MatchedData, int Score, bool Success) MatchString(string query, string stringToCompare) + { + var result = StringMatcher.FuzzySearch(query, stringToCompare); + return (result.MatchData, result.Score, result.Success); + } + #endregion #region Private Methods @@ -144,6 +150,7 @@ namespace Flow.Launcher } return true; } + #endregion } } From 24372eb76d5ddebdfa255adad791514c7d041bca Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 15 Dec 2020 20:10:59 +1100 Subject: [PATCH 071/442] installation and uninstallation restart after done --- .../Languages/en.xaml | 6 ++--- .../PluginsManager.cs | 24 +++++++------------ 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index fc13f0c2f..7f8557c28 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -6,13 +6,11 @@ Downloading plugin Please wait... Successfully downloaded - Do you want to uninstall the following plugin?{0}{1}{2} by {3} - Do you want to install the following plugin?{0}{1}{2} by {3} + {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. Plugin Install Plugin Uninstall Install failed: unable to find the plugin.json metadata file from the new plugin - You have installed plugin {0} successfully.{1}Would you like to restart Flow Launcher to take effect? - You have uninstalled plugin {0} successfully.{1}Would you like to restart Flow Launcher to take effect? diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index fc4abc800..c141916aa 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -36,8 +36,8 @@ namespace Flow.Launcher.Plugin.PluginsManager } var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"), - Environment.NewLine, Environment.NewLine, - plugin.Name, plugin.Author); + plugin.Name, plugin.Author, + Environment.NewLine, Environment.NewLine); if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"), MessageBoxButton.YesNo) == MessageBoxResult.No) return; @@ -156,12 +156,8 @@ namespace Flow.Launcher.Plugin.PluginsManager string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}{plugin.ID}"); Directory.Move(pluginFolderPath, newPluginPath); - - if (MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_successandrestart"), - plugin.Name, Environment.NewLine), - Context.API.GetTranslation("plugin_pluginsmanager_install_title"), - MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) - Context.API.RestartApp(); + + Context.API.RestartApp(); } internal List RequestUninstall(string search) @@ -216,19 +212,15 @@ namespace Flow.Launcher.Plugin.PluginsManager private void Uninstall(PluginMetadata plugin) { string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), - Environment.NewLine, Environment.NewLine, - plugin.Name, plugin.Author); + plugin.Name, plugin.Author, + Environment.NewLine, Environment.NewLine); if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), MessageBoxButton.YesNo) == MessageBoxResult.Yes) { using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); - - if (MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_successandrestart"), - plugin.Name, Environment.NewLine), - Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), - MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) - Context.API.RestartApp(); + + Context.API.RestartApp(); } } } From 1fabbe9ead0ab6abc5dba845094b0d0ba5ddba2d Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 15 Dec 2020 20:13:02 +1100 Subject: [PATCH 072/442] PluginsManager version bump --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 327011ac3..0353fffcc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.0.0", + "Version": "1.1.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From 88af30207c594aa99c59c17826cb3aedc6e5b849 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 16 Dec 2020 21:52:00 +1100 Subject: [PATCH 073/442] add context menu for PluginsManager --- .../ContextMenu.cs | 60 ++++++++++++++++-- .../Images/request.png | Bin 0 -> 50820 bytes .../Images/sourcecode.png | Bin 0 -> 11842 bytes .../Images/website.png | Bin 0 -> 133947 bytes .../Languages/en.xaml | 8 +++ .../PluginsManager.cs | 3 +- 6 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.PluginsManager/Images/request.png create mode 100644 Plugins/Flow.Launcher.Plugin.PluginsManager/Images/sourcecode.png create mode 100644 Plugins/Flow.Launcher.Plugin.PluginsManager/Images/website.png diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs index d26c1ead4..f071e0e98 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin.PluginsManager.Models; using System; using System.Collections.Generic; using System.Text; @@ -19,11 +20,60 @@ namespace Flow.Launcher.Plugin.PluginsManager public List LoadContextMenus(Result selectedResult) { - // Open website - // Go to source code - // Report an issue? - // Request a feature? - return new List(); + var pluginManifestInfo = selectedResult.ContextData as UserPlugin; + + return new List + { + new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_subtitle"), + IcoPath = "Images\\website.png", + Action = _ => + { + SharedCommands.SearchWeb.NewBrowserWindow(pluginManifestInfo.Website); + return true; + } + }, + new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle"), + IcoPath = "Images\\sourcecode.png", + Action = _ => + { + SharedCommands.SearchWeb.NewBrowserWindow(pluginManifestInfo.UrlSourceCode); + return true; + } + }, + new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle"), + IcoPath = "Images\\request.png", + Action = _ => + { + // standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/WoxDictionary/tree/master + var link = pluginManifestInfo.UrlSourceCode.StartsWith("https://github.com") + ? pluginManifestInfo.UrlSourceCode.Replace("/tree/master", "/issues/new/choose") + : pluginManifestInfo.UrlSourceCode; + + SharedCommands.SearchWeb.NewBrowserWindow(link); + return true; + } + }, + new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"), + IcoPath = selectedResult.IcoPath, + Action = _ => + { + SharedCommands.SearchWeb.NewBrowserWindow("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"); + return true; + } + } + }; } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/request.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/request.png new file mode 100644 index 0000000000000000000000000000000000000000..a9126cb9bd8e06e3a0772e6405cd0e87c053c265 GIT binary patch literal 50820 zcmeFZ`(KRt|37|JRJMaGk{H!CBP3KbAST4o|0*lulj*`o%kam99X4xi2%I`uEy(3?J(lI&aw|+&NtI{Y!g(yUwmhHDi5Ik$vC) zOqE4_zp7*CxW!Ev!u#U!i+CAX){cig+sWl!;+{*g_BSjq�~L-@m>Yh>ChJ?fcWH zv5&I`zK@=JlkCY7xrJQU|trBjgno_ymqrugD6_(d8;tQvC!va(hv|LR(DAs&?1l>;Szh;c`4fccEP9#RhRIgDb`F&`t5ad8N(GAg z8ZDYpr?@G6Ki2u|x?KUP*wAzWz0zLK&2U%H4c8XV=cjhDu^BN}|Dz)3i<`AOsj%Qi z@pj%cf{3`SJAtUxderx=Krg_V97&(X8Z-*n%bV3g^pWPa!ayd&Se!w%4ol_{L}enR z5UnR@{T%A5mig)F2BD>7QT88uPD+i`GUuAR2%jddg0v^oFF(Gq=F{~GMfVCT7h$Ai zZg%d~6zN_y`wKPB<~63f&K>Tt?X^Vdvr|;Hkywk&3(cPi3T=t#OqJp-UcwtxSh`f6^sG2X zBz9&k-FIxBvy9g$w&&plJq=(@f2v0QCR45GPbt#%gmNHf=l7GxqS5( z%q0p(mXgm?xCMOYiaV-A%S3w3V^FnL7{RhwE0mMWA4F3T%o3Bp5} ze&d@*u5!Dt*6rdHo#a~arbc(kF7l22ht57-z0zU=;jcokF^uylEU4RH?V8Tb7AO&y zg3j|tXn{7e@;p0jU`w=Rws`2dy&=QP6W(IR0)DdbHG25 zUG>waePrOt(!+vD#F1WC%2H2!nQC1v(SoMKu-x?A?m2bX$13DJ7fa-#7niRS3Ro<) ztJi&As>>5nS{*oVA?5VaT>dDtssS&ucV<>?5`HJmQ-hTWlSTB=ii9WEqH4 zadiL7JV#j?e}j`tnZS=AM0Y@n)?YF*`>RuUF+aWX0FnKNdKy$}&%&NYJ)(BqVr4nv z$b$NvW~7bqBa6`uN|N>+S9DcB-?Rc>RRmLNFSM1#ikyX^rk^iOBuuo+$nZk`yOfNk z(7OcDaU2Y}#?f48485|40@BB;4CY^Rqg7UuK#e9 zlT1c#UEix*NT?iyKDY})&umI)I|lQVhLf(`5rWt*ok(U;+?C5y<%mnP#hqE7kjmU` zBwkH*fly-Mzi(I(xxL(K_5`A3AFixy>{RH+{MeW96AFxX+A40eS7QomDyY^J1Bcq&A8@zqpm z2x&FX%&R9!j!0C;Tex`3ui6=E+>`vff&&Cm_+h`=BCQpFl^h|m4?s7vV{wj_?x21< zmeCs=w6sG^eM*)wk>At0^@D2N___v*0(ktu;qQnnRHI<7g#iH0M2jLuO9I<_9BKgkP z?EGtWIbscceC>txJNJ{xZQaM;ti{WT`?udM-FJ5Q3Dx>D%!Bg_Ox8yT+Or!f zY6KIB=*9bT`OWDWSA*gSYad8C`nQC($5fY|_%FziP_>*V&SmAPbm|c;%W-s$hVS)Q z{$%G4kw~7fRygP_tYhVsi12gIICUCV7IV{X7M-ed$OX(*|bmf=E^)eec(o@~Qo zR>Wpu8qGHI?s=qAO2*hLb-C!P9I#dIN_=*_lk6D3kbX0_#h5kd6Jq`l65UN3$MKDm zZ6*u5+pZ0Y@K{=#%&fZ6%RL6w?>GjgYwFbxL{jRh+$iWAVIlP*iy)I1*!%`9(TU2% z9d1HBYEI7{z!}*EepzZp6AzSm(v+=)F6oJ5sZYVshG^1qp*Vt-=S=g2OCMWF&ux}t zcsxJ70>Y>*=wgabv+{P)k2lhfV>R-Zva0NbHZ;3U0*%w8sZ{F&i3Z`X4N;%5O=xqX zymiH`^y6#(uBp=7RO=x9_$NFrKMi=nv>=63#=pIBhI6=6qj)m?SRRirANCmhMSOr- z7DiKc>dOg!dP;^%a6Az`HC7|KOQpEON!Z7<=zuJVhSw@o*T#^_daz6#goPW{?_`jH zr@7J465`41dEzCkyjdXIgf@Y2gZiBlN#hjhOqyZ-i3ZM2tUMOptEGhZR!#9`k;$jH zeEO@VLYUtIv(ekwI2X=6dxmX+geAU5%^9G%d17Qb&1|o?VD@idc1r+^DjnHBj(VYiCzqNK2cp&l8q}Ymk^hx+qLziyr)^r{ zBvf&B*#X1jXu&AB_6cp_l%=kC;`qLs15>3hs8%&>?4gQ{3*Bqx+{oe^(?YTieCr(| zyU3O&G@%^U`G-A34#MO+Z?+Kx=UY;W^bpl*hVPv*roXrG`JAw8eE0G1y{mE&8dRK6 zwLS;E@K4n+B*TyK-_b@$NPa)ck59`O>&G!Gps+34dTyTnK_|Z1iHz{L^)tn9Ps>;Y zrK(*6YE8Wp+J2(OJcaR{^y5&eD*1d7oFpD^q#tidXmg>GPL&>`*}%yh`$ZhU%3A{I z1i3>x+Vkso?p0Gi(e<2`>@`OvN7=?ku^Sw~Hzf$ZVN!8;0pF*bjh&D5|3nt|N+;u) zBmGTg{Z213>y(sB%e6M)rh`!6TmcNQH;HKR!50f` z3znrw{~!;P)2D5Cdzu>aX%w6Dl%vNWRL*Z!)1_Q8ew-Ee=YYF~YwVAm&md5dOD{N9Y{@T zZvq1@6zfyol09-Q&gqVoj>75G)xWK-rg7Jf2FLQx4J4m?Cw;S<+LQIt?=P9(Q^CQ7 z#sRBI!!{kmok8{;3#iAJtsYaQm-yFNg@ysSG9QK7@+vpsT^j!2?FxAN{zWL`Zs%q9EC zN{?8B{bL_{n+mt4uPl*ek4;~?@8^KEYMCoLtYcsFds8<~bA3q9N>4e{$9~Q(4wd@j z=}PowFoq>Iq%wQ=$ZW&HE3)oR`w6k2xoIfZ*PivNW2yoUapT$H6Ac6T(d7s+!jUrm>8 zAZyyX1G1JZUBUli?)_>Yoz4@?mBj~;`_hBEKYngBf70{ zj5W1}zSf4LUh63-n>5%J1?mc;WL;PPQq&K**n#;&D7Jl8de(# z8LYRzLL+~WdA?ghWIr?2+8(t)97r|w{LSH#32pyrcmDZFmvsHvDvmhYy^l;b8V?*M zk*c=V$q06Zb*)N7!#h*b2f;@%_wA;PFR_+a0!y~~2qR2aT$3H99tZQ}EQU`~tvaG9 zL<9Z09NBMUBhKb6B6;b*J~Y56VvY)Wxw&j*|Fg2`uXiskfAwKCbpvUb#fK0w>s9nJ!bi>bcXa}?Xx1@`TcGnW`^-<1EfOHDERT&IooGl2C=b=Fc3(zvxdbqSFhTF#%5 zl5rKzUCTmIFWbSkU`|2ZgH^`-*YI2c@Na37Rm=O;}a@_lQtO!rxb9jkzzi7)z#T0*-i9r99%6zd$-u!hjD^DM% zvq!eoU#^iqoyGV?d`F_0t&ns|x?jy(fnC;HTF;okL1t@4o1}pMJ+1TvUKrg@%9NwJ zOjrHY1kIkQR5eSilW~P8bx3I2Ld{tyuAn~c9U+W9DUe@3?9gqofB+mI zfQ8~_iF)?oria~{)jH1R$Fq*f54u0UT5*D3#jNrYen3+G$JNuN5kE}h1x&*n=H)~B z@OXsLhh0cmq;II!i@@o*8?r1)|59{+R^t-ud7rrdK;OybZn-|8iU`)CH~eMNyHsg7 zPo+ivd8%|X8HEI)FG;Q&aUqF6PrQ^0qp2IBsMbDkLKDEcKY<2XqpO6sJB_SkBgoii zpT=%oA0i}wkCkxU$s$U+f;YK^la(qRpjr#Ta~0r5E}*gnYxJ2&&h(M{($BYsjahl; zM2m?IJvzWPDGj{+bqVQgwTE+q}DXS97yvtThUqo zx1=XNrJZ3Wb;PSoo&k&Pbs)|ld}QCTEyy$$uj47TXw;-h<;a>BJGT$A*C^Z2o* z<#v<_pDzZuc4bfm!ztfKdKwL~3mLDKWW~66S zcP9}hJc#BVH1hAdLB|6Kd%53FNl*MhFvx!Gg;lI74`GaCPHn#()#VXVDPqWT!tkBr zkF1Wa^cKn_nzh~Q$gC9ZN*{lD|G#N{g06i(s?(L76}c$D*!#KSS@75Ys75|xsa9;X zrP*5Htfl){Z_Saf+=sq|Q=cg0iPJ?EwOU?t1LCRH!{AgalA$foz~y8Omq++X)ya5( zYGebozHLP*yN$(dl=px{u3;Xy6a+45L+n9%Zq28;X9Nmbu{Dn2rxnZML~oM-8*vUV z;Z$aXtx!=ihod^jE95@}{L{dMYMZ{BzSkuK0SWcPPV$Y@GMe_{73FarB3k$|!z>$f!0UW3HGWYkC88iHI4#*N-UfP>ZmE63^kR>RKqa zK;2C#nx@NSc*bD|r|^@4iCQtuPBQ;S@igEm)3u}AAs6kHjI9+1qNjY$;Jc?}>=k^2kDO=`UJ(JDO{aMZEXErN$|ug+ z8B>D(#tSN6x)hzR@)71j7$-ZC{oi2A_Zuqe)gtTUkyfxmyCF?7%h1g0C4lN8WC*sx zH>@ha7S%d+R^H1bLY82oZ1Pia_$7W|IXilF_znKMU_hQkgadO5I(%EA$vHL0Ekss@ zoo8WZAj&9GCzMC1aAOpo$IWj1Voh{!%34yI%rC0+j9z`&$;Ao~KH`?{gv8AWsix(N zdke9L+}ON;DDrMv2H;jSds6Wh7okKug{G=!&O z;%b#9Jwmlkf_~NBA5gfgvp^h5-QbO&$ONm_lPG6T){2W>jNNQvKOUkJwvR(5hde1|RVq=v3kH<2rV)IVLJVYF9fSyiQ+w=2*a>2wixy__`c`r8!+`P6= z&&@{YV2TQff2^xhM-A;)<+;;iHB5XFt{r8u{O&z+ue(dMRc-nJ=!o0UqVtMnltq(B z{(hZ1vnpC;Ve~^Ng#G1e^-n(3GEh|VIfXxh`VgW8VQ*Z&)05Omk*>nwj_Wve9P`cS2@;KQvNmPPis+V&&Y@sa44^>`r^#-OxpS! zOxaGuce-i$T2+4v9{IvJrwRWez%Q1CbF6z~^rdnyVFN4gxJb|16U&cp)Va0qra+E&MTDYm@7KB%7p>h;Ash+UtylUWERZ@?ZdbXt+> zVu@J@o;Ogvw&hJ|S*B`RE02uORTwN$w-&mys$z^$kL;P|yEl-GM{P?m@nk94D3-_C zx%65S#T>mcmiRB0K~oXxOOKXQfIX+Ew9t|45v{|~`yu_;N89ULG?GtCOQ?#^6Nzqj zot0Htd}N0G7T!7vuVHa#l+;h!D7eIO1RifEpuAseTKX#t;NSR@(+;aZCH*HQ&HNu`y#Hb({syr46owz$~{}y?qtV> zUhYNq1b>vKIEs31BPY3+1PWE5XZYF`|h#rf2n*L!S=GK>SBQLRfQ8rcCb z!~i^+E1u0$3V-E9CL5ptjY}%`JJq@u3rQ@TkA?G&-afF{rRb#8%zLr| zA+Ew$jDPqRNOQ{VqUVBf68I=JxZ{p`(IM|ARlQPYU|7P1E-{BEDemZ(%l#BVy7pZ8 zGulSk|O%9?|V)b?KYhV#RHxUAh5l z8+1`3gfl(Lzs?*j!aKvF`O5|Rgh8f;ZBd(hR^%4pJ*Y`R$vfjCg)Lr{EIEGr33~9(lPPq?TE!|mOF-X zet4Yze2shuXY>C=F-@Dxdbx+Ol!c|nSjueBRc%*d7T(sHKt#JYiruNlsZs?<<<%37 z{8Z7D+IjEeG|b^~)yO_aMCLI%v-@q;x#<;4l*i?mpwJ}w~&{6S8;!ecgj5tRB6zlyRgfKy0J}F zp2$8AH+!n|2tTmGvnBlbPsJT=3^~=Q*frF)AnY{MUTMp_Q>?sPU}l1|!nWX3s&p?t z3w%aREt`=$RV%J#zMdP~$-GMxGS;dD*FRehV>gV94E{5v#GquzuhL zRjxVv6(kZ8<{9l^hq>Jo5a8hQ2sAa{!*9GqiMIloA?|^&nPFMdH2y>nn@}-$p9`3KX|HT zuBw6U+`khdAvl)kZ=L4y`42=!{;BIiULO4wzT6-^WB)N)jjaB>_Npq%@TgtvV)t{- zTY6|6nzW=;z9=zgcbB1fn?$*^VKMc1zS;S1nj?c7Ld>0nY1Gxwvh1baa3Wct9bLUr z)J>}??{|>EP3KsLu%hu}qM&rRF38Npg-mp*}hv>2B2e%xcm2owr~pHr>x0oaL_wR&y`oMczNRH+=TNLm-E)@J7JH{kT2^Tfx8TmP``!LDNUQ>8Ha6C)GJ z5%^}HSoHBA?9D=RDhb5Che|tv*6EOn-(im`YvBwL!~fZTocsAa}xqF!}5CYW{mQL<<|Pf2@lB{}O?ML8mR zE@GhVdK~lNvXj#Jya_`(sg9EBr!dml?EoLDt&KJ;Xm@y6@)+XZ}-x z|9+E1Fma?xdOmcf-@Hp+X;~QjsrJygKtQ$t8qN`qq{;7xf;HEdE^cOyJdxeQ%5n~te-H7U9|+` z4)`8UZDuRHO@|okbmX3=N~`&yU{J~+vr5E>iQJ}f=(Ztq@I{h!?m;JE7oGX*fzn*J zkjgbk9wXoFvB|x;w0-~zB2_9%+*Yt+x$=g6`TSTkMAgB_VldK1*j{h+`>mOGg@l09YFN$yA1j?9om_5@LbZd~yb&b+8RG^lCOwx$?MsgGmTO{hG2af<#_-{BRcRWLr3c!rpF z(dyL#t#wUF!71VZ4`$zv$??^=l9B;i4u;`lSCgIo4DgBy?dWpC4nHEnNa)RAXE(WxxB04~u zA&C;3Kmh>}M#4r&u$xYo_V3}t1F0Efwvft6#rJ?l&E8= zXwq#QX9#8gx<*tE6M_LroMN-;-!2r#3>TeSIP|^q4&`!k<3MsN^Sr*;mTc#Wk8cFr z$*;Bv*hJ1E(VS2){TwARJHCf4xu@J z>(HYp$M36Rr%E1L{Rr<`IIFE2Yng>sYaS{)WNco4m1a0r~M*v~Jva0GtIdxV% zt5Z?~atAW;5G73I=nPNRY(G_+t7x_LU@?EV(~ELL^Rq9>qd+4+-APs;P|zGPHXDWi z`2=M_i)S|ybff+(*l}ccl2(~UKFak`*+*T{q?<^q(_CM=v5#=b18}e8e|1E|QDNzd zT}1)Q49(8>C21Ye$Y1Uho+VK5)(I@tXsNracU}>8wr!T{iZ5Vk^v~KDBo@=U;kez1~1Ehid(5hZIX{&r@l@ zutMM|=`x;jM~p$7i7`wzom-CAjr=IM#6y*qehzT}HD>k`(2ws;#W$Jo$l)TI{+(I-n5=pVTY(m8Y(3zt4MGyW18w zovlxVMTf4hS}|K2U1CY5$PKF30fjt3l2?ne-o!!u1iospY=4<1RZYh&eI z@0^nR^Zyx@R{lNaZglk9Kq5RRI<)HP=<6yqy#ARSS!lL&`0Q};N{ zLzmw-e;xZON67e*wqeF7`JxN4<9^zk53LU|q2z3ih*=!&)rbeqtllf8QZ0+8B%5s5N$7{@XCL#MflJ!=P>f+aJdLMBe;|cb>`gs6E z^Thi=%TAR01F#=jotHM%>^caS>UT#+^SVa9u~T@AAUDUGB_5(S_%yl8M>oMccnCvT zRqZ072K=sh)JEHcO({}C`b-l@cF<8}2^#jTKNZW)@L@t)=ak0bg{e)%f&bJbNt&N? z_&jHsimRHO^?^7KBZB~U^4-Ztjdm$Bw&hc;V)&oSfjVE0fP{}Xk@493p|G_Ey>tUL zbxnJ#jjTMK3jMQ(vd$X)t75t$LADe5HfQJXYQ5$ZZi3)qUHGuVKN|Vyp=4u+5>y?2 zfgt_%^Tz9D6Un>fY?EciJ5<@(wQCDV@d#863TT+f6yWSMTkN^;gZXdKM+9S$MO9Ay<+v9EWj@M_C3YIIsU z;k`m}^=Fg`cS3q-6{r2JGk**y&o4NWU$sBP!~#BFJ3dg=HM%VghvB5F^d2Pro|K;;~J!U!&MZqP%ahvJHh%ku<&`)Ii1Z ziz^)VLnR-cT`B5pAr(vt*^l}|+vLu(#)k6|E)+&dS8nc#(Ale0i^%(x z?6}W}Ib}`?4c}NjSzMJTP`LKUj>(weI}D7Bn+qat-SVi8EU34ll=dY75Bek6~5e4mlBy%m;bu~${L)M zf%f!~H0fHNbMx-IS!TS;Q7b;W@|5fc?8#k-i+kfaH}~0Ies0;VUi+^oeX+mG;~9SI zP9tNg*T@e<=6oAtkhcL9&{wn6UOA;+tG6tPolOp?keM$fwT>8qSS?Xb)QMf^+SV6( zvlbL~9KXH4@2!o5xZfB>nlGsz*nq68(vRR24|v{2zhtJ(_dZG8`ZofFvzL0M3S?j~ zkHfc|>brM2ZvvXam*WRw`DW`bJ?q*OBCBR!Se+?Wuf2KGR(KczLaq2^h7wUY)%PM@ zX{ua*aP8w&v&t2;2P~+_*GaXG5%rAd_r4kfc{>tf`GU9pq*8@`$AvkLS%Z?=LpL`F zm+HA0Uv3r=(G8gF$mVZgGFSm>yvdhJi`m&nfvoCTv%N;%YUH2qoT8A#mG#KMjlEYWP=>kbjtD+p7UgtjWEq>NpNDn1_$Bf&G`T=JOs)F|9 z>t2Z@dM+@;0`Vkla$ojx#o;cF#=xoZiZIgKQ%}((Kg7OpIPl_R0PHk3d2H<0-0j zq;m>gx=iHv!A<_$OmR`*je((5_z_Zvh{N9^sjH`?VZ6zjzdQ4+oo{*y-+@1WEr)4_ z-z!%znFEAO51c9J2UC28vrAc}UB}$|oe^X@w$nsWVSnS+*R4BvW_~;W5_}79kBV@2 zmV9_4jC%0S@Ee+x`mE9QKP!pKp8qqf_um=3yOLTI%H#YFm@X@0%je$KeWWlv$X+gB zoBTQpke}-S^!vZqlT2kkn6d1EyH!sg*`?^<$Ko6fFI z@$;hj0mCxC6SAnNX+Y9mqroTm`EPu_e=I8cJF4pA-KEsk!YIs;4O4qgayN_y18RRc zQp!MYV!l{~x*(WnR|1E(jJIl5oQH+A&<9F!6f?jX5s$a2+FHQqeb#v-g)J z=u%YK-TdC=pyV7LNW(-&oky;(7UtJoD>V_W61?Y*X{y? z0DZMReNUZ`QQt`C>bxIrC_m2lmu`K}fAb3hgB<<9k2l*3J&~v-V_e7F` zie)6GRXg?8Zj6elxxl}Uiy0CKK0m>%5WJSIs+E@K>Gx zPP)lAa_8Omt39dT_5+lyjdkDl(4u+1SwtHn=7pcez%`BhLetNUx|KVssHv`zMi|Zvp32Je&Mb4wT3VLGU&}%hR}OPVQQ0+P9#P-yVpUWzD>3wB*4Gu9ZAI}g z8Eb^;L$|pz&~jv}Wv*K_Qm{FsvH|be(#42AB|`yO|2?N6JLd`hYX2phKN+gP??rL` z{$&VfUO)sUI=E44idhz>7%C~x_Zmnh5ghczfCVPzWNhe(tKyfZ^M_A|xKecWyJ4pq z&w3UHaaD`M)A&UKPZMs@j2(ets5S3JEM7#7bxUenFib0pV_#4KhNNJHO4{O*@nL}u z#y{(Zzs%j`)Uh?TT`-3wp0AzpQ6W)?lzfrcm2yepB5Ek|9@)Z;d!bnte9z_kO60Jm z>G8Uxv~SuyVrfVmDBBW7Q)o71Ci?N_x%cn^`;BScor z-o9+ORRsg0CqKrrEL>t0O61~|+H{<;|2#39io8iQ1ZbsAmU ze2>u*Vmu>u-HZrpVafkqrI8OMyCsUr5qLI=*Yj4*c$6z1L}C3Frh~_^N3YdDd-byR z?sF1@W3#+kT*`J0^*(4eUC}9fdnw}uGOj{JSF_!cJuz(`(U1;vCSV6U*#);kxgUBDdJQj!gZg8e86Zew`yO z)l9uR+Oa+2YwsIoRr}}+`4dAORI3BrBZ9MU@YR6!Z<~d&XTPNwyiRg&sO+04Ayh>+ zi8gj+#cZMwqekw)BcYGpHMJ|OYw2z)N=ITb>N7{{^lny_U~FANTN`DmFTP79A@5xJ z_xHQ#*riEly>Ff+xmL~2*0(IGn;DkOi|6>ynJ1p2J$pw?TF;~$bwr^J!hfqh4&9ka z-I#AC8c-Lb24dj!@~*bWi#Mmr^~yXKwRf*jN#gid{2`Ll`nw(eoO^1qKT`tX z=SOT1{-)=q5=rUS59~pDIjIvy6dEHLW* z4qP%<`=+c`EAGlr>64RynJwK%f9Bq;d?|n2rF&9N5~7{8a5a>#4IP+xfqo_GNME&A zR;w<2j7s0;z`X@ow&i(z15&0#s>l>IYfGEjyWeIp#RX3@yAik#DaJP_w3^-M#n|R5 zv^c9gh37gMT7RdW4bdYsk1UC+>|sDKZDliK?FU{+2yO$)xnO=kMmora!<)S}h~7;n}~878@lpJZGQmZ|6)S zj0jE8OfrZwhkH;_YdUMgD_AOdwn=tjZ0`B?A(NkyA9t_o&=v2e)VWG>Er+#NrjFQ= z`Hr&le520a{15*?xs?cvhGZVBY-2tq|3e)(+g8|Jo@Loj2lD^4OxNe5KCrol;-2I8 zfW@YF?CGmTa+mv#E>;dLvrU+N>eoM6c~QHDQZy=iE&`0qj$L;Tq2Dk-2WtKrKpmAn z^F0k9 zzkquVba9Y#xqPtlqE_6Nh4f_poF16eO4#IS_64P7p-L#|xpm8DPCdJhJcF)EF6WS za0bUpm=T?fhx#L7KQ>M)et4jQrVcX`_7dg$lVjJZw7DuVUW16YsQx~{T6X!5eS6jW zE2-90DvZbD_;(Gc*M2imzTRrb+Wju6cHK>Qy0h#HJ8lGzZ|w1u(W1o4^Esvy#=B#P z_Qzfajs!=%6lvaX2vxi@iL!i?)IUDcUx@KBTy3eJJz}K1CIo~L&*Ca^9$I4B{2W{U;5NwMbz;%1o2Y`gLkS11c?7)*LuG8ojyrh$f#Ym~ zLI(p~H%9Eq-x>psBFSWUlrD!0f0I=ENGtB_yW2GOme4JRDJPkQ{iyR3g>^G+(d1Zi zXX;$9At!9wp!*0{x;*JiGBK;a1T>1%LG<-QY^*J9I4dC>F|Jf^mNEFLNyBh#|4v=S zob3nV*~IZrzwn5;Xab^(=e|kr`2=Pdmes=|KjjsFnEgh%-@QrHyL!!I}l`jaO?LR#?iDKt!r1RQaS0{&6p5rT3 z>YIQ9-Qlk(=+9o`%y5<+W6Lk8tfR;LqsOIp{KFI;eFf_5t76FMKMc66j1kfoA<-<| zcl}}vn)76Mnm|G26Ajr!Q#cws6J^1VjLWgz^Mz)kz;l~1J48f(!IP_jBqr2jCicuP zQp8`AlpH(i@z(<<*hDBMs!*1f8$SKTmCNJQWsawgtDY}bUV8UHr<}*N_LPjz;#KU< z9}047s;wG^IF9pggVWd2%N1lvHkai9>lXz~28-S()SPB%I$h-FsvQ{HOcDLrZ{oU4-Akaa|v@C+{(0_6zRNEZ=h2Ag-fFne407zoz>R zu3~!Z$@beKIS-Sr4SVuDe^B{3@(K${Ez<%+L=*S#!&F)4l!$tR6-AR#anKEXefCS( z-#+iz&GhOmu?BHl_|-?oP~TYiFZ>Q>4gbXyi6J=|MMfJ-2^2Tu?GKaFePme_ro^#P zS1sNG&jt@>*}3qsOEU(-`;xG1;D5`0g=I1$&{v%J^cjP`!-f&1U5??0!L$!*u#iFK zc$iWHhr;vNgvy6IFv`DXVJK|!)m4AyVM=JWQ)eXVBM2kAh8_f{pwmxJ9xJ`xZAP*M z`m#71|N8@XK4NzF^`I(-Ssgg@R&3M;Kc^$YM09}Z`vvT5SFN~)z#Z?~t4?HY_W9%J zC&80(7RK6KzKAhyXOub>K7NYR$X63hiKsJK97+58z#4P{8W}M;72`KqqKiLQ(gYsl zS^JnMpFs0Pk(%>PQhT!slFg|^A?W{D2zOkvqUQREu35hDIu|hV*zjgERP5OZ9TQ_- zT=5zG3b&8te(x2?c9>Nl)KDrH+j54kii-6m>i3_$f#W^;ynLm)hGVV;}k)y0?B&kop3teG*mAErwTN3C?s+en1zkg5fM_O?n_>;Pd z@f(y}kgTOyIhof%iOqnl{!q2eEi1vMpXTt0hL_6Ys<2WqNBv6H&o8&04cm{IdwU$k zS3|pMd8Gm1( z(>X{39=T4kKvi@9m!ODgDn}A)F6RzI^IpG-GGkwMZ=yLkPTeJ|gjshxUXMwl7vZI+ zaD_~E4XSag)n+uLknc$k=43zI!&BmX-sf~V)w@T%{};uahflX)PFwl@({_wkK%ADt zGvs}6VTFjc2X>bL{T&C5B|G39UL;|7H0R;|Q#kYo)La)vj=QK64O49yn?kny5?}?v z{4tqFU%EU2{Bw-HEL@v0dbw8`$eXYbqiZ-2y;>B;UtycQwhNez-tfsCM)dyuxol$D zjB#X?yI6TIF2ID1W4Wdq6#vV2Y=Dzz0SuXDU#hx8;dq7eM+#tm!UhFCW%dP#euJ&_VpM6!mD8_6*2cbB2@_eum$ITH-Vf%j{#Wg zZyi*Rc0ft_y@j{uP{)&BOq7kfU`1z2a`E~bvMDdp$6v3%^@VzLn>!wy#yrPz6nz$B z;c@!!>EIDs@6#iD$6sA*=w|_RzE!x5KJ3&^x!rekOEKYM=opTPqRgSN2m9cq-g`1Wjj~RG&#~Y}$ zz{SYy{L3V3_M`7xqc&qQ1D86pr=Uw*C7N;#H%*Dq58pv=y>4j`8@w}ED8p&e3Dma0pKSz$iTfkj@vUB^SdPfS&BJ_d(Jh4aUR}9gAYAfV=_6UjMfBm zjHXB=%1;hHWJH@ieZZh6msK!5S+MT|U#-jASXNt#6y!9wf^S4$LCt>Hq+Z<7!iaf~ zW`nNS9Xvet?qT0i8~<%cUpOx6dzG@EV$AO}H;Lw7X+s72o0a;U;FD~kBphXhv(Sod z8e7ifLHY8n(s9wkA-e)o6sNd{#=r322P$cD@sw zwt)u{!W0wNJ@q;Rh$h>XrE*b!IwsLbbfxWm>h4)VQ$MdXQGW3JHRRS9*a;=o$RA~n z){kR{!8%I4s9AG$)WJ4J^kxf7OzcBfh9>`hlfX^-J0U&ks!_ zbtV^w@HlH|5GA`VP$&tHW@j6v@svt6k|!;Do{kr!$u>2QAWNrjnR5TjXQCl@;CRMc`hp+c9j1;SSi~KT{lXqznC5cdnntIunAy7I*>cR3n08;pCd;d z?CP72(+~84S9S?)+Eh|%X&z0T^i~ms1E{~S8f}z<%mH*DZ$7Nf!fOfC-ZSA7rxt%^eGB7Tg@8< zIN1zt(U;*v*G2tmh2Fu;DokS`N3|SgfBlXiL=cW0&ZwVE(L$_Fr6S)-Y7^1=9N+zE zC%zxi${@1Prp0{wUaUtdH=oY2w|+&$1Y`8mVy>A_j(J{`kCS2z^=*}aeP+Zf4HT@OR{ zpZ5DtCu69-dHOUM2G2}kmJneRJ1T6g!TW5z${FN?caI{K6m{SbAqlod;mu5s^$ z+7)~P-!3Nw;V{XeN~Tj{TTI7iqtWHRG>qRgSPMGb658xof6zg#R{5Ys{XmyQS@pqw zYEc-7DGC|?k`!%Wj{SEess0!eV2XL;vvgBrhC9x2?M6GEJc9@xiy$SggQ;3<3tf!n z*hj%baiRY+qLSqZ>k$mLD`4lf^iCt7gN`gvkZPJrw)qoycBV)Hw7$ZL0f*D&PS z<(eq3-R()U+nq-oDT>S+F{Rf=HCD%mcBqqHIOW#0V84hZ`jKdhi~*yDce1tGg_vp+MY&9ytWkKy>=+h zV-sZw3j-#}c8&Mg4GX+Q=kqdz_g30_&ew{Ydmx5Qw92ltiQ7}qkS48gyARtwnY{n{ z#kOkfQi_Hlu4&}Zce*zH?0j&^dECH9ZRrj!o2{ZR)qv(B&%8U$A8-E-0d6s#d`f=} z3+L--pih8f+{rIW|Bf0Zf)-1?FzU>aG!&$N5ucN&ui0h6vz`jBN&QiW8HNdH=6S!E ze7f`$?IW{SSKn_{E!+z;ZLLgZXQO~Jx1$}Ko(EYM@EaVcV1QPml5>0u(G-u9egGMo z-`5}n!j<6GBm8!XrFYh&j(icdk*1UO#9DmJduSjRyQ!s{P z@TPDTfdJ&3EmbuekJjoTHLh`V(n_z;6QtSt>WHgo5$%7m@X_n=6n5dRE0vPR-B^bH3SohqR=bby+WeK6NIK92tLeH&g%>Kq&fNn?nTG4!qT1I!huIbR9SX-Z) zYJ@RRjbPRS7}|=12ps#(@Sph>U5xHWUv1Dk`Ge%4Am((JE{4)+ysTN=Tn*Q<>dKwHe*s_uDD zw$(oPVb#ZP74-Eb`I7v==?6}YpQ$er4U?Pr$Q&9v-(oRsxN&yRm2$eO4W9(Xg98|2 zPOUHW#lCBclM#8~+!l=+K>&jnO|Lu;DuqQfQf{ZK;Ka2@%b{~Y4ji}V@Rv>_n7@%m zXmqq<|6rEiZM4wrgo1Htp5X+-oedk%669dBK&2d+b-0DC)EE zcgEf+^n=llv2pjc0?+& zYuXf2MwDckk{V`DCSfhUK2CsMiO062vm0 zo1lqY%6Us<4kV3S^|7JRltnskF~Rk2F0CkaS*7tTb1TQ3rQNEtwh31r=YX}GxKb4ZJ z@7z1lAdB06+TlGgpxYaV`B>4;f-*_s};mjNd{^At4BaKj=g09ecyNxz9&aM2d z9;$@kN~6Y)kH%k)At>P2r8|%lC#w&Lg$q z0KN`A7F8Oo^L8KDKLmI5zl3-zkDktPII%TieN!d$?dux6m|L%846l-Y^fH$18cdyH zQXnPX`kt`-DA)J)qTidRc&k#o(ul20ihoY5#aH0?%RlC;YbT6;dHgl)FmaC&gTkSL zM`VrGWrQjy4B_0^Xxsb}GgJ!JTi!nNo>lBuEUF?-^1RZ?>x)`nlaR6Gh0sb&REROQfUo#2Bg2=>wnk9;5ujcU)wRlB9q*{u45L0 zelKwif2UYE-f>&|#Ma8=0skF)DC0mBrlN09XTZ2sf9q&bv-1&+F_B(WXKG_zQ6;(@ z^X#1zDkN*^Z}|?%ZIvNn$9O$K@@QV&V<`ObmRJXsNiC7~dMv{+>_c0917e%2kD-=w zd?_LRT3*z)zISrZ!yeQJJkTRq_YV*{%-TtyAwtC7tSS576&Hu=0{}Ex?_L~+leoBO z->9Lhv{$knAqvf4*6mRb@h`Q%50>s~L_B1LrVYd-S;vc4q@GxMN!#Z8z4QURXo+&Y z|Av}t%I)In{9VgBe(Sk34xL`e!4ZecoMXh>UdU}Xnsan_FlG5TLJ$6#%XD~Pton;- z7^f#PvxU<=7ESkTQcN!o*x$y?FVivUtWH+UlaMSS^ zU$CDJ1`8GD=QQ&}jDk@Ah;w4Hz5=>Xf*?ps80NR~s$(^%nfA2n+Gbi<$J zm{duuo?G(78WET=meAusit#_+!6qlUxrqgnE=`=#BS|nqSM}G)JvZ^F{|WT2v-vxv z@-;E+D2ov666>iqPzN4%(Tz7^04eLvI#s8B+Z0S1KZFQ^WZkX_VnVQxB7K@z<}HuC zn|_;EHLfHifQRGw5g|#7k8B!=-nayRTi`tvWUe%deE$>VksnMk#!tj0pJg%+mb@Zl z#lZ-4Y*6`3?RojVJjAykP~F(U{4|gKppbOqIN7IL|b#YI*+2#30HD2=_m6 zVxRE9*SD<$1c+j5FabZO@pvpD5cVu?h@EJtB-UwtFsW>EwkWQQdw`k@b6h_3-QnJu z=Y1l!pE8d$NHM_gHw$x#Wqb_nH96F&+xtxJxySE%|9m}CXaQkCNC}0-#03}d*y|5{ zXn~>P|J{rI^szm9HkQa3#sb$|KQFt4pXcckJ4-k0ujeg_JiU%iL|(J=QU2Ivz+8jc z5y?%rt?@@N$QTlBoivS=E=yzVVGfVS_1X=8e#o48-=j1=QWrhZSuN*&{bY%UtRo(xiHFQ+wR=Tio4{6L zfLmlM*`p@zb8%@2i$-dH$zRdxPfuwiv`4^Dv7)9QD4BWCdJe3|2nwk9BQWvMXZ8m*xCzq1B<#CeT1+G6g5~+!mA>ig+Wz6Dlqvhvr{!SOyhz z;|2v#j0x=Q$s+7h?hou|QFhG*meYStAC#URLS%(Mj(heUk$PBOLoC2inaziY1;C;{ zF~W3u`R2Q@+L0G!$Pa}agDIDKi8;&0*aP}KF3~Q)5evjqUN1RhGeIM4n^y_hBD3y< zxQ&@sT@R1s&ZjJp9QPqO0Slj0mWSWIQ`XiU1%V@=8U3$WR)@b+Nk-dWx(6QR>;7!G zx~TIwDuT#>6ih-}2vYm|ZWDJGLW+>w8?=Tc0VIA+Be`&>#>Kd;6_~~Bd_RhD9|2U8 z8lvLysEzu`ZsE}W5CgnXy$Gtds!oPPYW@`X^PL!hTZzMykKX(VH z|Nl-f21wRuco?OK&P|Yz*!Krg%|5%@xIE+FK!5Q$_n9a+(%%HZ@SqJG3h9S5gGnKu z1he1Om{|SuL1A58DPg*f5Y!CRCPDZ#z6BDAiwiC05`ysKbK3>ZJ@2}SA1`OrW3hHY zpg2QY{Tc3A(a)Mt_;0ZRyQaoJ>ss8*Jt2PRhF0AUJdx2Dq^WBs5VJ(K~;6Ra27x%p61c&k5W-)%# zh*T*dhb9)QYOKMTzSvL151%`Txf!L4|NP=TH+~cDab4nd3EAGc=b6OeETYTLJz!1T z{Cuh@a#xKf{}403t%cvdC7Ha6>(b^wttbvhkA&y>$`PCITb#+OK6~h4E@mf%L@qEB z%jEa}zwvK^h_N<0A)D{$tUpH#eHi}y`gcfb4Sp&)a95|Kw?e0+pD>ie=p)70j=>#S z;ziBZ^*KGU>Q*E_SSG#RJhk=@PLnZkvcn?5@JXlq zKQNcqui}Au;11h52X?Gl{J$IZ<-Z04p+f_zm~HzYbnBUgya?fQ^5%kBDsIt#5X;E% zv%_>=Q%;;)Y9q3Tf6Ne!2{0Buy?DYGcexXG`)qms9SKS?sowzz;E&@8lUGSNr}Pgo zG6YA;)J(7R$N$@h;RXXQSHzy@_R2LiPrG_QUwg4W;C~zGiQLzhW7{Rq}!~?`K z;LJ4fDlOSVHtWnB9{a@Za){hFWaSdT7#-XBDXovM#$7JNj<34)^fY5s8^yD=6#Rq3 zcEWUid4<&Ao>EGdyHnm-{OoS-Qw%~;gNY6t0^FNWck5z3NpDwk_eNJU)^&aqP>Rjyt`=Fq8%70j#7El4RD*>EMOY&dxI4b z_1szezq>!o4E98`(dh4MGYb{~w{5)|ibiY#sbhrnI}uS5&%TD#w4e#B9){hFp`dmI zdsjW?7tR~j!w42Mwf^tNHz1L~*scGsrL?R)*JH+bd=O4>{Q#4%vbYEisAmC5@k@}3n_9aIE#5@>=^@Er9%_Xog z)Q<`0@B>c7?aq$>Swa+jSLuIikny*m^6(i)iZL)gsHzAwWHh| z_!YYF^L1m%l!9{d#c()2E)b(;*9g*fY zz%`Za`+w=6SPMsVe-cr{1spMiuRSVG;5|185d0Y0MqBC5=)wI7)K!Cl@LIJ0O{X1n z`CA_d8)ALmv-n-u%u8~S&|G$#h24>0BGOghUwI_k<=7X(2C#Ofp@J)viwSxo7J37J zEcm|v7y3Ld;}H=?{EK>KexRk>abg2JQWGdZbb(_u43&BzwviMtp5I^ znfT_kl630H!1AN6jW;)xSh(O&9(ilRQ6pm6+s@|6PJg z!|Zi1z)w0p^T+rK_KKzG1gAlaF`tOnaNo{?j2X+#06t!Hg4`(rjdg@q&nZfINbn7sB5!r+BF5 zZR$BfL@$?SqT|4y_wyGy0k=AtyZc%A6~3aG1^$PpfQ5;t0KFh0@h$lws*jLp#Qg%i zfJ+D5@s}fWJs#wNlRqq>6eLxdeJ8|= zo$F)~Vt$4nX$pb%ENSg?z z`%Y|Ca;R7o2Bakph=faH93N2JnJm_na_ag~LM%);^X!IE6Rrhhwo`{sEj;xF^=~|J z=K{649{JegmpREWPkR(~4Mdu4);JH~`MGesAyTIKs+lmO_ zPD}js0v-Y%;-{oaM35FlaJc^xR{*42iea(e5Ry&QbK5?lArcr=@1>ldK5%ItfAE)6 zIxk(z_p?L+jL7ANk=QrJn%sWqlkWJ&6$Cu zRPTL|Y3U23oe`2TC0p(vMbhTRHJm|c?iyk-K_kFRBGq+Dr7dm_pV&&UT!9&0j$$ zq9770p*F4eI2b?tB6lAoxA}$GfRq~4L>9anA;{I@4&aqDgfbnPs-dDC*vk~%r|^Cdcts?(yQ8{rX6pb}+b#HrRA0pj{#6EJ{O02#uWLLZ2LB2@x26d# z5fE_!oWMRaU^wHp!8o(f_#KR#<-|p)GZ&@7BPw2nI{Po>2Vx^l0ASw{{4N?H5d9fg zjqoD$@thC%EqB=r64YQu!CmU*_*czE0ttI3$G8Hi=syKh4dwuYZ!iM@uAwed(7LJ^ z0c((Dykmy2e15u5I^hOGN0l0t_=^zw^#ww0*gX_Mx|ezPW2%pR0e<=jXHN9~cKx&2 z`+Pa*t0;BsRHWZ3Q^2D`zo4x0t(&lNKlKQ7^Pu>DqhEYH>kdOHxffAIY<)?Hrhz=5 zMttmnRRR2qr8rGIwo^Eh@NWC{K+;3c%8HzOd`w!GcLBor^b9}c3_HBBo|29!39ULR z3vaixl237hTa18=(L+Jj zy5QVLm5i+tHLbAzr2omUfH4qmZ~Oh8FIc~l50`@S6#Tzu{FD$K+oM;BJT%O#=}BT%_3&SQIs>iN zFiewi6OUmTyqdteD}P`8^e?a{*4{{Be;8XiJ1tnPE_ZW1}VmVf@8*?Co22%MRr3f;2+GiX%$*Z0h|iw|yFH z3+ko^h`eP-M{iTW0zuxxjf785R>A{n)Mx4rw}WyBC2OXUa&4@1U($#*{&%U0j-QML zPHoW}hk?@PIHYVv(WAGy^XsJ$+tWLCM;iTd#YsncIUnJ7{MwHEqSg_*gBGZ$kBmwW zb~r11SQ8vTVcj}5o#yZo?><-@NgU%6T6FT$i&V6T;nz{hh|&xJWtHRQGyPV4335S} z0a0hjUxkJ-iITTm2_JuTSsL7oVp1@Lm3u5O-be-`z3DL8fAxK0D_k+X>N`qExUxur z5W|hp&z{`-XwS?{?;_UF{komvR13=3C~NN`4&H8REdTUsQeR|>c#-}5*xWsw+}@8D zlW=W7fdkC0o?zS~M0Ivp3b(ciT=)z_^(&Fu?$69Q#Gkj+*Dj*xDr>7g!X;%?sreCced^-3uNl^~Ia;c@JS_8e#DK`ugS2_`wyZI(5@eWTdIsHHGNcNL>3m z-PIq^BT;X6+}5Z@T+u$EdeXHA_e=urzHk_X6*MkO_^BpJ-3o>0W6f608pvH0sYn(U~d`9%tS#NavPUuk! zhkGbPy7lD3TkSuAPD*XyGLCH|8qFednPFmK__p6fURTL}1 z$~aGe=ynq*?VdD2rvc)>2gHwx3!!j`gJ>OuPmh|9c7bpqg3tJ^SiCXHM!;P7d{2)) z;?5#KH}yD7oDKpIu?(lUfy-`5fa7xp@u$X(IuMRZ6r4kiM4J01jfl!T%$j>eF?|B% z9!yUJAMeWMiNwDqYA1~jZ30^=m&SN@!n<#LOEB|#&2EleV%#8T4sPeuw?=xONW7Eu zGz2+{UGJz^%90piwhocch<=z)M@h-H2pjP@%xpZV?cO8O+k z>kpvcZR6t~lU}LwOqm+ zbwp|ty>}@tY@Rf^gzLV;W9K1jYfwYBCRLH!-shzV){p`|I)*A#ylX`J z9~|Yo+<2!VzecVQAI?}uus7T`+T7`^ob~PQ-$N71IO9ohK1(jVgi;}OT1A*p$K8Gu zEzp8D#6q9*;EdffLPsMn=b%9+)|<&gINU^wz7Hy9ln2Pg_(DtO8I(NS9H+af#(Ed8 z<)6pb!<_ik&<}reiEnZURrdl@z8QFYhN}Ym1p*8zpCDzm-ucS7l2cFYs_Vtnl{yih zz}$rCvF3Bs8qrfKw|)L*mK;L~hD~t_$D2IbaXb38ZAoU{)`tm4P zF4xfO2I2~lpLc?7+wxT6`qM?bP zXBFZ^lW{OE13dk+|j%~dFc&$Nko~hj=%~{zy?@cTgp(v)m=kx4u%1MYsWcvXMFT;3!@k+^*$8MeXoL7 zYd|oET<}{hjZHj!g--jb9(RK5ri%ph)>lSCPMYG79fEj`-IPn$3!YmPLJ+YJ zAD+5e@>Hs^@xzOf`K6_Q;S4%xY~mJ7K&___{-DpKZQZGDDmoF4w%x?SCIl4ZXln|8 z-h%VEta!ukGc-UyYvYIgpQ*-U7q1;t^f*KW)b8=Y8$(t0iSxby;d*^KZr@E1;2(l+ zu8~dXiWrP8@%==!_r?rIm(zU?&3lSt+Iez43whMn+QP|jj@d2k`mO=cV)JXx_@$nyTBXCm2gz#?(xdtofJvmsCn6In&Xo$>iioE4Ir zqD|Zv5&MpYgeM*m+VzayXE(LaZRd}Oiw+#cFv1-~VLDrEcwNm1c)j01)OyY3h^rh^ ze}>xjnVd=b1lo;5n_p~Zu0unIn40``aAnQbdVfn`RAjY~Up9m53Wm+>fXw16C8%{IWl?1x@eOJ-p~2%Aps?e;rR~ zp2+u)z5d(uuHuoLBV_|nGCxZ#9I26M8jz{_0NSp>J;TpS3*ax7qs_o>{)=?N4%GBq z1Oud9-aR2tF%Rb?bt=unhp;&$Dz*`yrFl;~{yr38?L)L1xGs9hk5Y*4UQ*0_oTRm{1OF+& zl4N(sC<#p=j}e+2%4ILR^&`-vub{;NO^I)p~igh#BjKeErz5>g%O@#8V#=+`wGzQgYNsoX}PDFraq^V3Rla zQLM|*#KRPQ)^Hee7r)PkJseHzaTE8yNG{y$(GWZoO>Ea8c(L9RW{oqJ__|wSYu?up z`$P#e`v);W=XVMi?gZ!lZ_U#e*mAu{Kiu*+ZlRyJb+U?ZY1b7Vfbm%$`INt|ce6;XC9N5cK&ghyt?GIN5psyg-$(YPB$29sbM`Bd%kz*Em&eeylp z=knrjv3Nqw7QCVgpXs_(n3^L-*dX^S=u76{KcP>l-76GA&ZH#IM(HMqfpqDSWI)zFUf?sV#pgXVE?^d0f_F8+~&s0k&i52$v8 z2-@4`vrI2uJDU?mM(`C?lIO)yBLO2v5^w30OVl(>UGChr45H*v~Cq~ppL=_d6Ao>8*bc-zN@~3Y6 zRpzc^{tCj%x()i%oiv2^#Jj?kT?6T{!D60i-0)^3%U^NW*zGc^0oV`kIO`s0mxdwz zJh7?==a1iBx)!o=De)aFCJ|6-C_Tn;pZWLVRR!-Q6xF^5B7lPlJEmQCyEdL3z1K*~ zFgn6yJk0r2@}(JG5j_(w%$!V6C@9a_jgKtQxL3q;H#Ul4b2Y=_iUnF0g2DYSTF5y4 ziK!Abbny?p0gVQuJEian`V6*~{%zUOI49tbk>6EeqbHLWa=MmHmH#bas(&FqS`a2m zP49RC`Lo0;b~MHk@ts)%vTG)K%b;EH^`(!JWw?`{KV!b6J$WjZvCeSbc(${d$UJ0rA4b$*;oWEX;Ye&D|G*Fys zB8DpCqU3=FX+HRZ!0F~`I`D|-T~lRW7feTnjnUM+cE}$7b92s$$}@7OO^j12&F|me zYsScQUtm5fEUq&A*9Nxxj~3Qh)zqEacE8}6n%^hQDKkDZYt?C6p$$Sg;gPQ|NBL6^ z4H#&?tr%!~+^4(v-;g(z1_j^K4%wec3iCC>RDUG2M-5&Qnt)7>T+{&1T&V$_#VS0j9C+_M$3AjTSN*(nzh>~!{l6t% z4C9>Sx?it5HALJZIg*^lnX`RA?Y#GAUdw@PV(Yh*hErQq%Jzvy{1al5+fHZ8o2@a_?iE=7 z>XPCM-31}^-1dQNvmBL;rH7xpPiB4VoSzpomWY$fm=e?fsBpHbdTOM$Pm!LX?%wyd zYI*{S*7nAFrMUPnqyC<|2 zHS==W^CP}tcE8;jQSV#ovdPEW#+<7CyGHJ#(#pMsnT1t}olio)!9# z!u7g;UP-P?WUn?`ZMcss;^t-Xthg)dsv`Zw&xM?7Oo}0066*GrOUk?d%&}iBK9O4< zm_wxYzzX%`G~==d$NNWK=6FWjv^VQ{FzSMW^h*$)H*3_wj#u<@i^` zK)l_Ss`a|=B5&4r@5eu1cc=N))(o^9{N8RTd|2#skU%1v#LD!;8 zS9x*NTe6cyv3r6(kN8Vf-K+t9Gxy`RYEFWOEFdXww&z$Z_w=rfr9bBV(J$NgRCZ4I ztKpN&)w}&^oEp|}wv!^ga%ev1MP87SWvrF;dH+(K!6_~^@BXM`|3nq}-)m0Q%WpUH zR#{Lu|1Fi3rfGO}8BE?g&AVJ6Cb)ZZsk5H$es{o>FRcWpw6(Z<#EMyr5~Gh81}*i&&~*-w^H9 z`QoX^lk3W(EzR-ZyiU)t6uzA9ie3|(_xn*%SYc~~vhH54t(vspn2@W@wAiW?#k`KO zs6%ASqE?qhG=nCOmAKz%RmPb_TS22HI$gTZ?skWM1GveYmm~DZuzg7 z_mg{0L^(z8p(G`1;DhJbdG2X*EmxhR5}YBHM0S`W-QlOE#j{EE^`youtJ1@@?#r%C z>^os!9+#zX>Ung-f*a0>lp|8mgkLs!L2GsY-B{Z#%&KA2GzX`nK7_1|xRzYER?@|3 zTE^`jRLap!UGVS2W?mCl+~fU=Tb^bllKEWv29xc!YHw%#RlqL$k&qW;laweaMKQHY z_Wwv3-wvDE;uF=U*1J-EyfdQ7QlQ+jc+5G;68oGLQ_E#Q zxMPmYK;QL6&EvnjD!5eD8c`S>8QN+sYRt_Q-4Esu-Xi|e#j(FimNs9x`+?!0D@UHK zYWK^}Z5p%6Hq&DIf0QZF%f4t^*(A|4mMx@@jwwuI>AoQ@!Gk8Daxjw!0$E#DlMI!;(dWHM@LKMD?tQSu`pIk>-Qc2$y*_PMLQ(?~^ zUeWEZ$Ki4lm31UqZZyB4oRXEkC)+xETnSq44KLs^`wgk%H8-f>0%7~9%n7ybpQfiG z_tZA%-)QEkvgez9Gj_MIRnsOP6K~I4rn9uA>CDIu>5T55;hfGar<SoR4;-?H-NjqdzGjb+@R4TEU=zmaE>o|Yv$E0xh3pXw|a zU*Id_d-GG%jUIaGAIe}GAx|Zz{$B}+lDd%R*UycWMXtH+%P@JhZf5j7i{MQy!BKh> zlSbiP0&E@{X3KhB?TU9Cs)WMljV~VzxOXDu zOOA6#HCNwcG%l9@Hj13N#JaR%ZU_Tb@UWQ}!#O}X#ga5|hIje;p`zAGLtRt9>e%f* z$5e|_b&_9>@G3o5~x z)TCp4-!MLUX`Ar)5ynfQ{!gA`HC&PUq@k5reS*a#-W+|&7OgVt#_bXF#{~xsIFV7B zSm*>awP?0g%Mko^hS$LK{+96g;b&v_YFjnZEsm&mL}6+1u~qdUZ-0$RR(i+^j;--3 zsxH3LPiniyHt8FCM$WxqI59i>^V{NR3CVWD-E=?$O`%73%|NU<_)vGALvOa#j z_#CKqEIaR?-V_ITd=1?!A1KM{39ZpYwyMMfyWDp5Y=x$!T@Q;M?P2s!^)I10M||h1 z(MdAo@DuE1f=Ysqh2I3$r8N0;iG5C*bz}*2rFO*U<5;lzCDS%Da=Gg~qN?7y&mCUP zqQ~1$UYqUAJ-gK7vaMQ};9Bg#pq0J`>H$}16jKvhwQ1oBMf}~bC-&vB=9VMuanV7q zs6*z(n^+&B4I&gW>E%zab6Z)Wk`WI|DZbinm0UxQEwpFcm17c1yhn8xj?;W)eb4)> z%6Y-`-$fZ$DA>b@yY!9I_@Y`@s;2_V!1?(W>4lQulM-Vb&I7Q)fpdoP2d{YndIEdvTR(4z=Zr) zugt9O0is!R$mn_W*6YdEls3Y)1g-M*T56o4-u-h8$DB=*@U65jx64*QI9-?5kC9AVl-HQ`=7cOxrtPTW|3DomV>Rk_2rWdql9yp3Em7ArosJ@jgFqhYf2w_#R zpg&{7+se!AU-zpfn?mA}7Qm@8?aLp+@5~FIz5Y16e&tp3VtSo#0H-jyu7fSl4E=tG zE#Ua*Sr&QGd7u4)37uByFWl!wO?0o}NaY2kY=|9c9{&}4BKnqj@!bZ2yr8A06C3tg zmmVgyEoc!7Imk%TGVu{ef9LuAfDF@KrQ6?|b@AlX$a(wnDGcqrN{nEFSWbj9_Syk& zP89Zqv`=Z6@ZfH?s>;aUA@XT0JLSc;YQY;z!)3=cv%MZ7L2yr>cP-8TqmJ%|#ZPw}|^bcHlQH-K@9OW^7B1vVTh6>N`7XUi2>&&6d7cMQzh8vrK&; z8l7>=nI}&B*TWG0OHf}Qi{L+J@EPKXZsQd#A+tw@v-AoNo2{4B*`C|}BL2<(-r-;6 zl?HvL56}6R(rde&Si#ZK7GqYLLECFzG?y~-_lM7WX^BmL*kKGEJag4Sv-;Fj?d2d~%Y1j9_nO*k(E$+L<7txBx z7JaetwsDO>`rK9B599~$z{Y-(sKBSQ$eF)~Y7ycAhb=d^oCj6&V@0!RDuXS_b+l{+ zt$E5R2GMrWu}J3vxU#Hb?0EB7t2v`1h*_;V)l+&5?xkfYD5v<4rzN3P<<;Z*GI?8T zySH*?<0-x~F?Ewo*TPsE;SQPhe@9t1QJ)H1r&eQq4&QLjpZjMGF@awD^6wUwt8^Vi z>QfG3oy`9tTs2M?{`0Ty-dV2mY1fZ!EOjQ2lc?yRS|shexAwq8t~gzD>gxs(lbh6nGSOyDmDzgsoZHEDMOg|> zV#+B9J)i73SLl3|Y=p6CVm^!;&hKCD+1fef#E7y$fRXXm95X=}`37DW229A;it%zz zNx!87G~Jrx#HKZpoTbOZYY((zGIpcM#AFb+E16rmBOtxzQ8YPsummyMs}I!~&hBf{ zXZ=~GyZ4IhdHCWw`vOg@wzfIl`qCVi3lESmY$_H zoHbC@bz1=+A#_Bx{k~wvk_=G=mzof-x--Q44P-)&GGcP>tY4*Ttm41im%)hnn?%mk zY4RLX;`}D&?{LdG=xm}S5?6`n>BYnoY)bD+&@z9Y_unPGUZzE{%v|dB;v8orV)7pT z*o_q4qV~*%I&R@d^1sDEzeW==uNI1}8e7!tr!Xg;d-_1x?7<2+wTsgvo-9pXv`x=e zt@uphxk7*Cl!PdCIKmfy46bQnpJ0D|Wc6X^{2MIDlc23$0HZ<*vk1P+swX zf%_GBdxm#z(eq$CH783;E64kh3B;@mn|X~~5tl0%!LKrmU}uWIpY^Ml_~OJ$xw3t; zs@528*g1D)_XE*E9%T3={E2tDZR8E-e~17iujOSnd1MT0cpow4Ag54%Jc%0>-7|T8 zBuqI)G)rN@H&M1gqhQ&r!3SIsy0x}pz`$wZHVMf2Y+^$v()Vd9U-)dzdCPA#^Y&VN zm^0QK#40z~?Ku?xSnS8rc6rs?-(hF|f!Jzf$v-4%nF}Y48BzJE5dR0y;FW{-i6?GT zP8mgl0u8*%vdq%62U6m}o}ZdF6tqyxHhC|tXWfxIM%ABwbjm{LVfMN0v)J>?JgG4g zpHIRv-ye-Wgl+xWE{kl>)ETmoE5rFp5 z&_D9-)4YbWm20{mI5D1Y#cq`iF@@&R()~-l88J8Egq39#RqNx(wrXSXiRTzazixCE z*Li;3?bQD!F0tW19r50zLlzYGe+{(_Wo$02jxqc27}?EQT~oVJ4wr;P?00hALH2xR zzGLkHYmNk)mU_AvyM8o$U#mn{-=oJen9HXnA%|z)KC2`QkouZA-^~Dc}8!*yUgBlvA+JPhD@Oaz4Yq`lp&@ zJ~`^bh|1Y9ar@qj#y^r7rDLUB>gMNlm~&!c1MzMn8z|m}wgn;EXT~i#z7^xXJNFuI ztGgt^4^rE|WoC^l+eJ!zu;r#l-*ZWuUq8VrLDNGKBE+&Bvh}p)4*(dEq^*{F8%Mo- zt|+<_R#JkNwJqi3)O!wOS?CE*^7mkTQ9j1M4RN5)zdbeeju zaMh$G#VYRKZb1YOwA9;Xh<6x}`rT&UcUBmrzWUzA(zaE)d*wPLX&TEW1+X)NlBjIY zc4bWAL4`s0gCm%{oGkT^9Te#sJ-^_G62)|C4A{-)Cb5dYLEY7{A4U74dQ^0kyMJS{68)UiNtdC8eqL;DW(PhD5NV|ecW9a(lo@)T$YVEOwS5m z*Zp7zIkbZw_aikgXz>P1E3?mG7fo~}S{^m?9&p7~Gn}f0Vds*fG7gGb3(LaXlcpEZ zH1_$B)V7qiDRqBohy;o#earb5y;O$$M=I8&@my)3Q|h^)Qs;rQyoTrfeHyo?!yjtM zDW`amWVvhg?=KS<#4Tqg5+@zeMO|nTM=<{bxHf`m&fK-%p0FKc-1gNI~lP=3zJRa9t)Y9 zozs2PJ-0nJOCbk&u=C>a1B}?4dw~E3zHpA(G3G^gTH>oI zlqKE|C#k;G71Yz!xsi|D3j6zh84LI`QgTejEppq_u*g-&{V9JIw5%>Omly+*V}>Yq z7yqdbM6>(lq|zsgS|e8KW?`I51!ezfXWQ*QRQPAoowJ`K#(MEk_XMyvwSpxD1_dg< zb2(dBwCw4m^IGoG(0oaV0ikZC5_ggpmn~~K}(C`@~K%RG9`!qyhPLeShjacpFkh^gBwGl?L8|{vOV$9 z+yXscJGXaS_3e$OWjhe5v*$NfSeLd@c2-+jEDD%6-$y)nsk z_O45-_KS||_)c-p34Gl5$BcQ(eJLyYT%nY5N?(_T`|V>w>*q<`F3KirxpeJ$G6hq( zP+Gu;NWh1F-Bhv+wqk~Mc_7x&N7g3tN3P7w?&0*^L`uUC9dXBb8P6~LjqQy}u8U^R zHyG)LYq#||e96Dmo)M!3`Dsi<`FC*_ZGJHcT|#`*<0n^0LWFdP(f{C7Po;8-(+0~C z)Ma)Lvdrj>sh&YxKQ6R9v zpekUFq(W?bz-dQSfas8hLb18c1-MH_osqF@^yzufPc)ETp zEbo7=m;HzAOV>7e2Co@x=Thmm>W67Gx%x8>0@?`}eYo`aUPjEpXVDGEJ&MXj5WwJl zU4VHkEk|By@+VLb#)w(~?9+(1Ua~AUtp3jzU%A4|RrA7Ci7@F|a@{w`)KANI3nNC+ zgV#TwCT9*8^^f|UC1nneTBo#hO&>zL1QL|0k`S#mMeM0~>6D8sLuc?D#?#m~d~>zx z-Q>D;tozuLWrVwUBqO4F8#qB!3SyrBj*u(X>1HWai8xEqwGuInK!#a9?Ca{Jmf;Q!U96F!%;mae?CmE zTMlIKaCq+V4-M8#`vV`iB#%Y5YG>G#mr{meOjkE*%!b_dJ1oGeT7h0<%N*zTXQ)>K ze1wl7VWFp5YbQ|8-SiI6Zb?FPA~A+)2t53k_2P}YY4DX-O2~l8Pal2n8LaI4fe@_o z#7n#(6^@A1nZRqxef_dlpSDAI!iiohiQQ%%jm(e|L>@dWm|)qkN}(Yu(hz zJZ$SLA)&eL_mFwKjXwAH{X6Ve%?xNi@?+PcRv3;5{(2n7dTe&A?LN<%ON?VN$p_jP z?kkt00!Vvqi&2@GW`J{TcNrw+8O|wFxr4WSO-PeUPOfW@dIkY#218#FFbqG5K9^q` zNjAb>e0?p||07}}yoe@%@)f3|y)KD*Zuzoqe>;vf27aaZQ7ete2|pWKMIzhr=Lisg zTNLF3X(v&y>_e?YN!OIAwfIpY?%Pwb@y|GyTHiE`Y3!B}=~;;>$b>-a4t}d<#a0(v zbA94dnt>c+f##whY^Ac+mG-xexWyF}E5tzIesVd@MS4SB*gXQa7`|<&D7-8j1`yo12huQ-) zP8TZyx@?z(KsVeUuaSBz5Y+r?@6m|bZg~g}pjZH_3mFJ~PyS-uV_7@P;zPpZ=l6R* zK5pg(BRR97#uP4xExCSW68@Ptb59yeB^&q12c4mwyVUc7g>XvC&Qi0?#IbB6HHJ~- z*#CFlkNNGHS0?w?c0Xdz5BPDCHJPoTNY^X|o=~`5IYo&?fhYa;OS)4|)nysQ)KMWX z$dVk3?Co3C1%JP!-#a89ml*yNvYQd7=yCJ?5}(TRq4M1imKwfP&w2@yu@cK#AARo9 zmuYj#iVfDKKNiqdCt;hCWlx&P)0tYaNavobn#J7~06gmc7pf`cRiwAH#F`OndJSfC zo$g*)mpv;+I=GAIXj6eSMIhX;%uJrAdCcOXeI1^RB7Gn06TTMl1qucGHSGT9_@ukc z9C~BcP8DPUX~}h}Q9c&IE1q`&5{UtdqxSbiO8i&d$!itc{dgjmxgp}%X( z%$Zt&)EF&5PUYm6=C~&*D%cztj*N&$O`Gnch9FWRYWd={HJAgch)aBpiFyBrpVg2u zQLX{y)OeA%*KrOcb!q7R{o)OHM!lp9{qZrzs|sv7(x44u?3_D8OB@4~x*w1Sdy7h8pl-Moj@D;8n?aPG^Q^S`LQ`uJa%i{n5?U`G}+>6Tfbxo(bjIC7Q zehfI>Y}%ZFNKU2l;>Rg>O>^6IP}Tr6ZN*$(z3@TmT;&wiD7!4Z1)h(AM*x4asXFsc zv0~nOTIfrv_M%XB_Eun*kU3_^QNnwuLl| zNG-XyXhJcYEI+-r%#vAXfe5={?~=~tzXIIqxQkAfAI)vgAOOy)UGJVJ6h94znf^=_ z>c}9Xh8!_SlhCOG1x`;CQ>(M0nXU;0Lu!0&!7k#3+0DFhu83$XS$=cf1ti^z!C}xM zazv}$$h`k+KC;wJ%;Jy`{MXxRBz!S^^fbMbzPj{(3K;aa_m`WcAKbWhv8XID5UeHUyw{ShqB}#)J zpa`?&Bi0!1<~p(RS+wX3b8R}4)S?B6Ttd=0zJn3_&BCK7VEyeg0dhGWVXB}~h}7fT zDBDxq{lO8^ zlTl9j@Fm8{;SqQV%79W=SDpD99YQ{-xcSp!r~W?$))H8nx#K7ke5;KH?Ezt6L8+I*c zNiv`gls@#!ZU4=lFJ(wEO)X8Xi-@*?b>{tO_8dEj5lDCb4ASHrV`&Zry4^W8D_m%N zrLUkb{Q5HG6bVUMs+x!2-Dc0l?bp+#BHf04LQG=*cGSbK!F%l;Oo%c?C32p=PMKN2 z_dO`3#3$}t(1m*3lDxyEDg{Ob0gMkazYTqb1~*%1SN%aw{Uvuo+D^^r`9jmitX<^< zKSE>_&;pGk&hbM8qL<`d6z0H)Sp+RUNfbo?2)C>(GY|OwF)!#|T;fi6950gI8b|X& zN`ZFv@aa=Oc5eHS@PnQ4O`tYl<)gFBttK6Ybj*j(Fv;3YVk<8Lr*(PiwjaI4ABvv{rlpW#4(Xz;m zNZeQ0_P*D!c^ zBGGW*-T^%qSSKP{(tW@|i%y8zESWd{DUr=_FCR*xRYz87`qt0>!%Pu)d`PR|_pk#?7;CJ%kw|lKEYr7AzMsH2^S*5B}9epPSA1QK;Sl7TV`*PG?Ucg>| zwp9ZeX!86>@fJZ|13_gH&ubfhomEtA^j_e-?DTk_7kl?~Yj?b`QOZ<(WYuBUlx?@1 zw;a$KXBMu1wMJlCaUShl((I7c1!BJ0T=DueX~9E4px~qq zFyieG>c1|%HZA>g-kE#18ozv$(={d@AjWdqUbJ!`NHWLD3*Z*SuTl2d%JHI|omw&A zP5W%IL(25jy9qE)owjO8)qBp__pho5-1UU;kVx98Z3w6X`Y$J21A4bdyjK{K4-oh_ za&eXJp94`JV!Z;1s{J0Q$W^j@eFADI0yd~GJ5MrdUqo7HCCP};;|_IkN%a{+ML#o0 zuHx-kr9nyddSXi))?))``v@2!$S?f`a7n)FQ(n%|`^SJ`Uik#$G`x&J-i=sOh_pVq zws?-+;EK$~z~01*r5T8qpr{bge}<)v!dJYPYl*3qqyAM(iui_{f zMpRdd3Wp|`Jk-&rZ&9>JOgyKkdMz)Dyg;Sfw6^;SYd%}G_au_~G-L@b=P~^}`*L;C z+c@4<^>EehpDlp>8%+@s8T+B8L^32*sFj&l`r-vkQG&{ ztFU0pn5JN-8LtL4K~PCREbhoQbs3DKhbz+aA0i=V>4EzZ8oqvvxX{mZ))vK<=(Np$jw%5 zlKgT*@NI`*-bLlBfSoR+nD!zfv5PXu)@xER0@ek+oBKp{U*q1^YA4~vW~f6Y*GWf- zSrp%P2!iF`2gFmC8q*|=0GgbR@}BE&-`ehGY_2RbunoUH9AN>}6Rr}|H^AKei-+=R z8l8vDH$@Duz>*C4-YgC(^V$s4+P&id-2{;00nq*sQTErzX7t!4A|*@{}=^G>~ve zFou;Q5Y0aQVG{|28jj8%u;Tj>`3MKsAWYrW##1bf5eW1J{$u7bGYP-bpZ*(AK>cH-s?VAJ*X*>}#kZW-ou+Q{f&X%JzKZw!#?WRRo1?FQ zn?^4~W)_xKb=}5RWMKHyVK1Wx(-CE@LP%k>u4 z_VK9@*ydIPc2{U)ubcP$iwYe`xe{j}LcOgv1zoxirZoj$%*a~^f-r9uc;2;?h$kud6 zJuMi9xmUltS1qOw;u4T!>JtR5e=}>M+_W;`g=cxsE9Y3Pc3%z!F-IMn0&?ouX8=N(5%0ael>t<2~G7xGwKMHm| z+jHiq{I8}Z?RHIbBS4h-wR^=}r~H)eIA6qywy4y(VXx;Vo@WYumFEYI_1d0_{lP9U zvZ!u+f~EBgTROAb*uhq`Ve=k`rr?~nU&5Pnur&T#*5Vw%f!b-Qhyxu>OXI`1PKz5) znpsq*wH{cQpIk903Ix2MshXy2#k}oLDAY-%{CD_cgxK&T+2zSda8xU=BH(w7>=+F27ux?AirB$Ps`*SidpB+X&3r z1iPw&M?Uv=z)54EqsK+TD1OytFt>Zmj{2t&=jyXJXJkF7W|)BeXzWl{M61H49qz-b z*pTrglHhqbpjOHPtm?rb5sLpQI5j88skwktgV+$>X*;!oV%BVRTmNa&8_QzjLZ*#l z3${;OhFJFIg?NGDL;g~3OsKCfacUPffxaq@KF!Qxc@Ysm-3)KWMX}&VT&K3*bj(dH zj$HQ9Y5>u%d2nnqr@;>UdK5qN!Sz{$_B=gs&74Ayu zo+oru<^dcAMDG~+>!uneC$}j@FsBM9ZE@qU&rQ+Gz)$OKS`d@_!?ZRl<}7IX1w3}~ zHgQc&l+0P5-TKt%2nh6{F# zoa&?z1FOctxr=%fmnuPKJ@1tJqwS~Nad;}b@77(cj*|T_n`Yj#A#<}}mzxtj?@C-> z{>*yTc2LK1-@T!79m?Qg@z+`pa{Sk^(+u8kwX_2M$al zMF)(<>fHBtN;?LYaw`{g8|d%geBP$mfYPEy^q3aF0lEWPipx)H@E$Rg?vDJ7mBHc` zU=@v1lpUoV8{vv~6?Sj<9Q1=6C+Iy;Wt4<&QTL#P;zA8_&vLxymj`2$Ng>t)9yrhV zZtbLG+vel-C7q6JeCm6b{y|S5D!>F*$^5QQbPiN;>mrbmciYHY(yckCK;oGxN(3%@ zWLsr-@*3PZgKxaXN(7yQqRl1gsOESo*9BQ^STL@T`R@@DsQI7PA90T}0N_?m3?HODKBeDnFJ9 zE}g!ElWbw(3^e-uQtnq*?@2-3RPzP-ZW+F5Q4moM;|`B&*_PBisQ+BYa?CK!L_0YXF1lBY z=1DZa&47jZ^}lmhM9t{gGqv;lQ_;ibki`U*mFu8lTNoiv90gx0puL}ut*VLF>>|UO z_xgf>(B7Xz@u-TLt7}1JY3m?lI}~D%a@iO~6E&&ZQNBz?t1-8KQt+_W^K??Fflv&>WIWG@vxQM5Y6w>oD#Iyl_%~?<`d25rI$!Z!je!k(= zAM1sm-k%sOc>XI-8yvjp6T;D|Df@fo*M`BfX>c|4N^Rfi zO@WcewgTA&!s^I~(BjR!yW!JXLP?RkUNmU1?tgc^wBuN}%SOa^9MP%C*>6ibxVW8Z zgq!x|Wf%Bz_i!D43|blQcAk4^{SSkl20DWSe8SjI2v zqj0r={~ifWtJE6wR{GG`KATfF&xvD?N6)TI=XJZxh1+2qyq|_6kwS3adN3ZF=V!l_ z>x=}{_8H)o2z)weUKqG3skku54CCI9Jo1(KDYVf|!KX@|n*H1OvO8d~&RmOQBf8ii zI_U6wM^_)!o@LO3PO40p*yQxwV#AYnPSbq?GZ##dh@6;Z2w5xAf9*&-IORI_C~nW0 zeupxPrQB3n*PpA3cRm#=ny_H6L;war?vk|4^RL?I-FPYXtbb53-ojPSq3jLQvuLQ; zaOh-s^KOe76+oREER;IEs z*}=wp&CxKfKl3!WUwrP#=3a3oX)(DqXKynz+~v{F<*! ztAe58wTdi7tSA(z>_m%mh7M&jMY}7^CZD)>VfnuOMWohM6#wB#kxXj{VCGMP9n$G! z{U~0o6xr7JNGE*>Rh*}^w=H)sO`m13e8Lr)d3V(r3dGj#q+JIkP4p9B9$F7(XZe-)MZAGV(FB4K!JHrIxR| z_mc9nw?r^H(hedA361k{etSZcLKydO(Aa^B5S7hrI`ZnT!Go7fFWo=%b=hLc+OU`LusGvft>r3HQKX~dW z4HcHSkq$>NZ+wc}%m>UN*@E-nQSbo6At;o7OyNj7AxA;dJF|o?afA*0{$V6>jUBJN z1l>s6jiCTnA}msDx|e0oDs&G6q}S=r)=EJ9$G{I$bzT z*#>RQ;jWXNe>DM_T0DnWF8PY8nm6`LfTbBj6`_ZNfbnroOiV$k=_!uLt#cVDMcoHu zZG4Zm7tBP$;ccvC974>v69}tp4i=)FWYFUD{mDRX!Rh_}PB708jR}h*=RDn*YsqT) zuVGItQ6NFZ&CLfp$^!kt2&}_A4o`CBH&!puLbQnS&h;N~;fs+$>L#(WzK@&vDFuH$ zcdmRj{2T|tJ@W=T#u5;WMEa}nJO73AC)DyB0nezmYN2|@V?atPKr?YT5Y@&RIfHV5 zYg6v4V~W0y%w502Hg56$S!oQ*D}EQ0@l+$T@YE zDP}hXE|&kJAz`r`wbEW~)fTms;m;Y01H{U~t$s`bwY`{>*`xCxYB|679#8&@xAD~f?qNWLK+rb59u46l z^+U!MO%nP^4qU~xU_ZG(Jyh{ty*S;!o&haE{`bNOmuLAdQp1a4#ulcOp!K${o@O=+ za)g~83w#bPmnr&0AUBTQ^*-d6gqUjrD=Xhq+R;Tth+nFoee7K)MvNQ5<7D9vp1QEr!rZ(BmBUYrr7Wda=zKSU97+TGk9yBnh@IJR-OM_kn zC(;!^<8{s$Mn}yLOJHp%(BC`T4hFkg{RCQY1?Kin4c*evee=S3L6`=S0@&gq#$%et zZX!nc?=|8Pn7$F-_A{bDc{EN4y+=owqSmwF>l=fbE>QG5@~js<20B@q4Rh7cc4V0f zlhi%d929bSMhtyM61NdMU> zXqpXsOthl_*vooE^F=8e7S)DnG0+MF_aT3l$001bI_U9xCs9vW>NYbWV(}ZOtMVmK zo+;XEys1&(|K#bL-U+JJfMN4EAwWBLy2t_@l&cjhh*j~2{PcvVf+2yFxH&?3jzHeI zh>Cqk1HQEJ{dUDH!i<9U__$d4Po&YbAx4(=axfJooZPQAb=kFjMdZEVcyHu#f}G^J zM!G=r#Dh;43RODjCsstKVi@h#qL)_=n3JfYAO;8!u@zAZwi@Xpo z4hB`h8R~xq{F4{Cx3uE{1sqMi9J^V}2LH5DcF-a@DE1s)qBYuH)2x++WNy&V>9Zeh z$Xp@V#pwhtAdzVxJfeqc#8{A>9qqa*Hb4}HGa-3aL8jhGVD59GiRyD*snr5Z>k8z0 zp-aXx2-A2XBXlp01)uIW(F@5M@t-fu@YIctxQX^n$9FcgA!R8iD;zovH z=3DHQLx2Xz#tvc^!jNJdp!Q92%q`>ayZz`YaFvjkOJq~Y!^7ZEQ8gk@qGLle7)(GQ z1uv!Ec-}_5lCGSi7$E|M-2auLQ$StOaai(w_n$3jPgtplfyp^mZ-oar94|i32(BkQ zU|U{(7xI$MBgjiBf7b^KGn#?FI@1d##gLTDh8$k6A&Ik66iBjXc$6g&qU`B+-0Ml0%&JV62H z`3tfel?xkB>b;`3Y#0v4#~WVTQXOKe3lGSq9{>@OqlZ4=Ez~X1adP3ILMFmT6S{Um zG{+(siD_|ZM;qnO%pKtB5H%*<=?5+$`H-(3txo9SIRL0lm z^5kgnm4W=bLG zbu+w!ByN7h9O$G+%2!i$4;C(u7P=}QpUPN=y^(-h2*e8Hx-V3PNrk&0kFY5gZ!;9S zpVpK}b5RwPXbitu5n#Xs_Y@7h>7|y5$jIz5f&A`Fo|&8EamOf; zs}*pSNkLIpx(W(QIGv>QB((#3QV;DALa=Ld;$>;W!XrEqO0?r_#@`S|4o4Oo4gug)n~P z629gSbcOV~DAF$e>lu0~tx%u^L-ru!$AVfcYM$v`{N*l0F#KB!;5qQHfxv6nJ^<&u zum&XHRD~#emF$-VlAW)ZRI}&}>=RA)64^oWf@cfc6KoZ*HBuDg@aM&_vP@Jcp~VtS zqUOgdkAl}Fdzc+`?|$l7&@d7Q%c$B(D<@=csO{_To-5%H1QHxmIPcSXue5!~fyB0t zGqSgc`G(#KcbW%(c#b!G z4`3SnS_|%XCFJ#WYEF*(Yn@ zG9GCNsJBFgY}^OB#MX3^vqo>;r5wdy8KJvUByP(FXsPT%+7epVTXSn33ITYT+^8U8 zY_fLmm>yl>ZPg9CkGH-#dux2@pmv|8;Tj}`6J7*z1;;qVyk)Q1yay zD`qP#%Rt3StIYFXBq79g{94J|;`K9wrtePC?$r}ow^}Bitzc#`{(~|QEfxEF596w7 z%A)P(INTIA-w!J2;xvt`xifz&-LoiV-!X+*f&R8pu5yB+p7>`tGubY(95?Xx5@$w+ z$ZWrPPHpl5ulSHmvyk3;Wxeu!yy9L#_(-&+g*{IaM1J|U?bEs4$EZ!BmEtH`Cga}) zDYveC)~$JXu~pmLVAxRQnAVzO1)Hscy-76VT0SN*<8=iQ7oj+8CB7tVILF~#C0N}1*{99c&q9a*sx8dnj;X=kyYT|s2lmtf>b&@+tEuJdF|V$y@9%EwzR1k9 zui5YG&swrd5c2hm_%R(64|;anDnuB_c>e$2|FH)6vqt@Y_*gx$-)Q0AG#brL5V%~h zW|bhpJ3c)D|Iu8XT|69}Jsn*=esXp3c60Zh={DWj%iGy`?7I7R|Hpvj)HVFIoB#I# ztGrijz<_x2hK(8PyyH`olM_+|H0$7ml!WwEg4A?x>$z)ES0`BWl9D!U6sUh>?J|q& z%yph&y)-=7I#8IFnl7;ZA!*Y(M_!6GSN+5Ag!GO4)D&-P53aMb^}J2|q&3#g4%XIP U3ySeQFB**(xMcq6d2zq|C#DQg!2kdN literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/sourcecode.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/sourcecode.png new file mode 100644 index 0000000000000000000000000000000000000000..8efbdaa487d0ab825010fa3f3e7331824fd6f207 GIT binary patch literal 11842 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 literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index fc13f0c2f..14fc2d586 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -20,5 +20,13 @@ Management of installing, uninstalling or updating Flow Launcher plugins + Open website + Visit the plugin's website + See source code + See the plugin's source code + Suggest an enhancement or submit an issue + Suggest an enhancement or submit an issue to the plugin developer + Go to Flow's plugins repository + Visit the PluginsManifest repository to see comunity-made plugin submissions \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index fc4abc800..3ccf696cc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -115,7 +115,8 @@ namespace Flow.Launcher.Plugin.PluginsManager InstallOrUpdate(x); return true; - } + }, + ContextData = x }) .ToList(); From 407ff8266661c67f58bd7bd0682228d0ef6476e7 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 16 Dec 2020 21:53:02 +1100 Subject: [PATCH 074/442] version bump for PluginsManager --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 0353fffcc..73e03d525 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.1.0", + "Version": "1.2.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From 919f72ba62e1b1a6fdad77d8f0c3bc74e26caed9 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 17 Dec 2020 06:31:33 +1100 Subject: [PATCH 075/442] use open tab in browser --- Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs index f071e0e98..76cb0f86b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -31,7 +31,7 @@ namespace Flow.Launcher.Plugin.PluginsManager IcoPath = "Images\\website.png", Action = _ => { - SharedCommands.SearchWeb.NewBrowserWindow(pluginManifestInfo.Website); + SharedCommands.SearchWeb.NewTabInBrowser(pluginManifestInfo.Website); return true; } }, @@ -42,7 +42,7 @@ namespace Flow.Launcher.Plugin.PluginsManager IcoPath = "Images\\sourcecode.png", Action = _ => { - SharedCommands.SearchWeb.NewBrowserWindow(pluginManifestInfo.UrlSourceCode); + SharedCommands.SearchWeb.NewTabInBrowser(pluginManifestInfo.UrlSourceCode); return true; } }, From f12ea653d29fe2152293460f72483ae397551d74 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 17 Dec 2020 18:54:30 +1100 Subject: [PATCH 076/442] move autocomplete to method --- .../PluginsManager.cs | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index c141916aa..ea94ab6ef 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -42,7 +42,7 @@ namespace Flow.Launcher.Plugin.PluginsManager if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"), MessageBoxButton.YesNo) == MessageBoxResult.No) return; - var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}{plugin.ID}.zip"); + var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}.zip"); try { @@ -65,9 +65,9 @@ namespace Flow.Launcher.Plugin.PluginsManager Application.Current.Dispatcher.Invoke(() => Install(plugin, filePath)); } - internal void Update() + internal void Update(string search) { - throw new NotImplementedException(); + } internal bool PluginExists(string id) @@ -75,12 +75,6 @@ namespace Flow.Launcher.Plugin.PluginsManager return Context.API.GetAllPlugins().Any(x => x.Metadata.ID == id); } - internal void PluginsManifestSiteOpen() - { - //Open from context menu https://git.vcmq.workers.dev/Flow-Launcher/Flow.Launcher.PluginsManifest - throw new NotImplementedException(); - } - internal List Search(List results, string searchName) { if (string.IsNullOrEmpty(searchName)) @@ -162,29 +156,13 @@ namespace Flow.Launcher.Plugin.PluginsManager internal List RequestUninstall(string search) { - if (!string.IsNullOrEmpty(search) - && Settings.UninstallHotkey.StartsWith(search) - && (Settings.UninstallHotkey != search || !search.StartsWith(Settings.UninstallHotkey))) - { - return - new List - { - new Result - { - Title = "Uninstall", - IcoPath = icoPath, - SubTitle = "Select a plugin to uninstall", - Action = e => - { - Context - .API - .ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UninstallHotkey} "); + var autocompletedResults = AutoCompleteReturnAllResults(search, + Settings.UninstallHotkey, + "Uninstall", + "Select a plugin to uninstall"); - return false; - } - } - }; - } + if (autocompletedResults.Any()) + return autocompletedResults; var uninstallSearch = search.Replace(Settings.UninstallHotkey, string.Empty).TrimStart(); @@ -223,5 +201,34 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.RestartApp(); } } + + private List AutoCompleteReturnAllResults(string search, string hotkey, string title, string subtitle) + { + if (!string.IsNullOrEmpty(search) + && hotkey.StartsWith(search) + && (hotkey != search || !search.StartsWith(hotkey))) + { + return + new List + { + new Result + { + Title = title, + IcoPath = icoPath, + SubTitle = subtitle, + Action = e => + { + Context + .API + .ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {hotkey} "); + + return false; + } + } + }; + } + + return new List(); + } } } From df454778ec2d6fedad6e3b57d1e39a5b5acc8879 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 17 Dec 2020 19:00:27 +1100 Subject: [PATCH 077/442] update plugin containing folder name from id to version --- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index ea94ab6ef..1b13dabf8 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -147,7 +147,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return; } - string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}{plugin.ID}"); + string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}"); Directory.Move(pluginFolderPath, newPluginPath); From 7e8c49502e38e9473affba6f57a793a6e89e21a5 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 17 Dec 2020 20:37:01 +1100 Subject: [PATCH 078/442] add plugin update call --- .../Languages/en.xaml | 5 + .../Main.cs | 8 +- .../Models/UserPlugin.cs | 5 +- .../PluginsManager.cs | 110 ++++++++++++++---- .../Settings.cs | 4 +- 5 files changed, 105 insertions(+), 27 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index 7f8557c28..f69f23f6b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -11,6 +11,11 @@ Plugin Install Plugin Uninstall Install failed: unable to find the plugin.json metadata file from the new plugin + No update available + All plugins are up to date + {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + Plugin Update + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 58ec5005f..43f92e7b9 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -42,9 +42,13 @@ namespace Flow.Launcher.Plugin.PluginsManager var pluginManager = new PluginsManager(Context, Settings); if (!string.IsNullOrEmpty(search) - && ($"{Settings.UninstallHotkey} ".StartsWith(search) || search.StartsWith($"{Settings.UninstallHotkey} "))) + && ($"{Settings.HotkeyUninstall} ".StartsWith(search) || search.StartsWith($"{Settings.HotkeyUninstall} "))) return pluginManager.RequestUninstall(search); - + + if (!string.IsNullOrEmpty(search) + && ($"{Settings.HotkeyUpdate} ".StartsWith(search) || search.StartsWith($"{Settings.HotkeyUpdate} "))) + return pluginManager.RequestUpdate(search); + return pluginManager.RequestInstallOrUpdate(search); } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs index 3bc44e0f6..c1af3014b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - + namespace Flow.Launcher.Plugin.PluginsManager.Models { public class UserPlugin diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 1b13dabf8..37051e4da 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -62,12 +62,82 @@ namespace Flow.Launcher.Plugin.PluginsManager Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); } - Application.Current.Dispatcher.Invoke(() => Install(plugin, filePath)); + Application.Current.Dispatcher.Invoke(() => { Install(plugin, filePath); Context.API.RestartApp(); }); } - internal void Update(string search) + internal List RequestUpdate(string search) { + var autocompletedResults = AutoCompleteReturnAllResults(search, + Settings.HotkeyUpdate, + "Update", + "Select a plugin to update"); + if (autocompletedResults.Any()) + return autocompletedResults; + + var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty).TrimStart(); + + + var resultsForUpdate = + from existingPlugin in Context.API.GetAllPlugins() + join pluginFromManifest in pluginsManifest.UserPlugins + on existingPlugin.Metadata.ID equals pluginFromManifest.ID + where existingPlugin.Metadata.Version != pluginFromManifest.Version + select + new + { + pluginFromManifest.Name, + pluginFromManifest.Author, + CurrentVersion = existingPlugin.Metadata.Version, + NewVersion = pluginFromManifest.Version, + existingPlugin.Metadata.IcoPath, + PluginExistingMetadata = existingPlugin.Metadata, + PluginNewUserPlugin = pluginFromManifest + }; + + if (!resultsForUpdate.Any()) + return new List { + new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"), + IcoPath = icoPath + }}; + + + var results = resultsForUpdate + .Select(x => + new Result + { + Title = $"{x.Name} by {x.Author}", + SubTitle = $"Update from version {x.CurrentVersion} to {x.NewVersion}", + IcoPath = x.IcoPath, + Action = e => + { + string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), + x.Name, x.Author, + Environment.NewLine, Environment.NewLine); + + if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + Uninstall(x.PluginExistingMetadata); + + var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, $"{x.Name}-{x.NewVersion}.zip"); + Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath); + Install(x.PluginNewUserPlugin, downloadToFilePath); + + Context.API.RestartApp(); + + return true; + } + + return false; + } + }) + .ToList(); + + return Search(results, uninstallSearch); } internal bool PluginExists(string id) @@ -150,21 +220,19 @@ namespace Flow.Launcher.Plugin.PluginsManager string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}"); Directory.Move(pluginFolderPath, newPluginPath); - - Context.API.RestartApp(); } internal List RequestUninstall(string search) { var autocompletedResults = AutoCompleteReturnAllResults(search, - Settings.UninstallHotkey, + Settings.HotkeyUninstall, "Uninstall", "Select a plugin to uninstall"); if (autocompletedResults.Any()) return autocompletedResults; - var uninstallSearch = search.Replace(Settings.UninstallHotkey, string.Empty).TrimStart(); + var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty).TrimStart(); var results= Context.API .GetAllPlugins() @@ -176,10 +244,21 @@ namespace Flow.Launcher.Plugin.PluginsManager IcoPath = x.Metadata.IcoPath, Action = e => { - Application.Current.MainWindow.Hide(); - Uninstall(x.Metadata); + string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), + x.Metadata.Name, x.Metadata.Author, + Environment.NewLine, Environment.NewLine); - return true; + if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + Application.Current.MainWindow.Hide(); + Uninstall(x.Metadata); + Context.API.RestartApp(); + + return true; + } + + return false; } }) .ToList(); @@ -189,17 +268,8 @@ namespace Flow.Launcher.Plugin.PluginsManager private void Uninstall(PluginMetadata plugin) { - string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), - plugin.Name, plugin.Author, - Environment.NewLine, Environment.NewLine); - - if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); - - Context.API.RestartApp(); - } + // Marked for deletion. Will be deleted on next start up + using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); } private List AutoCompleteReturnAllResults(string search, string hotkey, string title, string subtitle) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index 0c647e6ae..e2e8d22e5 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -6,6 +6,8 @@ namespace Flow.Launcher.Plugin.PluginsManager { internal class Settings { - internal string UninstallHotkey { get; set; } = "uninstall"; + internal string HotkeyUninstall { get; set; } = "uninstall"; + + internal string HotkeyUpdate { get; set; } = "update"; } } From ceab9b2ecd8d4da48ff30c8fc8587d6df8138207 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 17 Dec 2020 20:37:24 +1100 Subject: [PATCH 079/442] PluginsManager version bump --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 0353fffcc..e970e5a8e 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.1.0", + "Version": "1.3.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From cca53c2a7d34d6d99c6a6dcae1ec17c018e0419f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 17 Dec 2020 21:07:47 +1100 Subject: [PATCH 080/442] add hide main window override --- .../PluginsManager.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 37051e4da..22682cae4 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -19,6 +19,21 @@ namespace Flow.Launcher.Plugin.PluginsManager private Settings Settings { get; set; } + private bool shouldHideWindow = true; + private bool ShouldHideWindow + { + set { shouldHideWindow = value; } + get + { + var setValue = shouldHideWindow; + // Default value for hide main window is true. Revert after get call. + // This ensures when set by another method to false, it is only used once. + shouldHideWindow = true; + + return setValue; + } + } + private readonly string icoPath = "Images\\pluginsmanager.png"; internal PluginsManager(PluginInitContext context, Settings settings) @@ -178,7 +193,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Application.Current.MainWindow.Hide(); InstallOrUpdate(x); - return true; + return ShouldHideWindow; } }) .ToList(); From c2ca29dcdae315ddcf6735fb4b74ed7557eaa761 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 17 Dec 2020 21:37:38 +1100 Subject: [PATCH 081/442] add go to update section if update exists when install --- .../Languages/en.xaml | 2 ++ .../PluginsManager.cs | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index f69f23f6b..a5be6c63c 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -15,6 +15,8 @@ All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. Plugin Update + This plugin has an update, would you like to see it? + This plugin is already installed diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 22682cae4..dad183227 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -46,7 +46,22 @@ namespace Flow.Launcher.Plugin.PluginsManager { if (PluginExists(plugin.ID)) { - Context.API.ShowMsg("Plugin already installed"); + if (Context.API.GetAllPlugins().Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version != plugin.Version)) + { + if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + Context + .API + .ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.HotkeyUpdate} {plugin.Name}"); + + Application.Current.MainWindow.Show(); + shouldHideWindow = false; + + return; + } + + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_alreadyexists")); return; } From d3e88c478a19e2638d461676f3d6648269b2a9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 19 Dec 2020 14:09:16 +0800 Subject: [PATCH 082/442] Add Programs Hasher and use IEnumerable instead of ParallelQuery since it performs better. --- .../Programs/Win32.cs | 81 +++++++++++++------ 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 2441b59f4..0f976cd58 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -11,6 +10,9 @@ using Microsoft.Win32; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.Program.Logger; using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.Infrastructure.Logger; +using System.Diagnostics; +using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program.Programs { @@ -53,6 +55,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var result = new Result { + Title = title, SubTitle = LnkResolvedPath ?? FullPath, IcoPath = IcoPath, Score = matchResult.Score, @@ -262,10 +265,10 @@ namespace Flow.Launcher.Plugin.Program.Programs try { var paths = Directory.EnumerateFiles(directory, "*", new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = true - }) + { + IgnoreInaccessible = true, + RecurseSubdirectories = true + }) .Where(x => suffixes.Contains(Extension(x))); return paths; @@ -295,14 +298,14 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - private static ParallelQuery UnregisteredPrograms(List sources, string[] suffixes) + private static IEnumerable UnregisteredPrograms(List sources, string[] suffixes) { var paths = sources.Where(s => Directory.Exists(s.Location) && s.Enabled) .SelectMany(s => ProgramPaths(s.Location, suffixes)) .Where(t1 => !Main._settings.DisabledProgramSources.Any(x => t1 == x.UniqueIdentifier)) .Distinct(); - var programs = paths.AsParallel().Select(x => Extension(x) switch + var programs = paths.Select(x => Extension(x) switch { ExeExtension => ExeProgram(x), ShortcutExtension => LnkProgram(x), @@ -313,7 +316,7 @@ namespace Flow.Launcher.Plugin.Program.Programs return programs; } - private static ParallelQuery StartMenuPrograms(string[] suffixes) + private static IEnumerable StartMenuPrograms(string[] suffixes) { var disabledProgramsList = Main._settings.DisabledProgramSources; @@ -325,7 +328,6 @@ namespace Flow.Launcher.Plugin.Program.Programs var toFilter = paths1.Concat(paths2); var programs = toFilter - .AsParallel() .Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1)) .Distinct() .Select(x => Extension(x) switch @@ -336,32 +338,31 @@ namespace Flow.Launcher.Plugin.Program.Programs return programs; } - private static ParallelQuery AppPathsPrograms(string[] suffixes) + private static IEnumerable AppPathsPrograms(string[] suffixes) { // https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121 const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"; - var programs = new List(); - using (var root = Registry.LocalMachine.OpenSubKey(appPaths)) + IEnumerable programs = Enumerable.Empty(); + + using var rootMachine = Registry.LocalMachine.OpenSubKey(appPaths); + using var rootUser = Registry.CurrentUser.OpenSubKey(appPaths); + + if (rootMachine != null) { - if (root != null) - { - programs.AddRange(GetProgramsFromRegistry(root)); - } + programs = programs.Concat(GetProgramsFromRegistry(rootMachine)); } - using (var root = Registry.CurrentUser.OpenSubKey(appPaths)) + if (rootUser != null) { - if (root != null) - { - programs.AddRange(GetProgramsFromRegistry(root)); - } + programs = programs.Concat(GetProgramsFromRegistry(rootUser)); } + var disabledProgramsList = Main._settings.DisabledProgramSources; - var toFilter = programs.AsParallel().Where(p => suffixes.Contains(Extension(p.ExecutableName))); + var toFilter = programs.Where(p => suffixes.Contains(Extension(p.ExecutableName))); var filtered = toFilter.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)).Select(t1 => t1); - return filtered; + return filtered.ToList(); } private static IEnumerable GetProgramsFromRegistry(RegistryKey root) @@ -418,14 +419,43 @@ namespace Flow.Launcher.Plugin.Program.Programs return entry; } + private class Win32ComparatorBasedonDescription : IEqualityComparer + { + public readonly static Win32ComparatorBasedonDescription Default = new Win32ComparatorBasedonDescription(); + + public bool Equals(Win32 x, Win32 y) + { + return x.Description == y.Description; + } + + public int GetHashCode(Win32 obj) + { + return obj.Description.GetHashCode(); + } + } + + private static Win32[] ProgramsHasher(IEnumerable programs) + => programs.GroupBy(p => p.FullPath.ToLower()) + .SelectMany(g => + { + var tempList = g.ToList(); + if (tempList.Count() > 1) + return g.Where(p => !string.IsNullOrEmpty(p.Description)) + .Distinct(Win32ComparatorBasedonDescription.Default); + else + return g.Take(1); + }).ToArray(); + + public static Win32[] All(Settings settings) { try { - var programs = new List().AsParallel(); + var programs = Enumerable.Empty(); var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes); programs = programs.Concat(unregistered); + if (settings.EnableRegistrySource) { var appPaths = AppPathsPrograms(settings.ProgramSuffixes); @@ -437,8 +467,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var startMenu = StartMenuPrograms(settings.ProgramSuffixes); programs = programs.Concat(startMenu); } - - return programs.ToArray(); + return ProgramsHasher(programs); } #if DEBUG //This is to make developer aware of any unhandled exception and add in handling. catch (Exception e) From 5ab021f4449e19c5d935f96ec7f5cf835b5c2d9e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 20 Dec 2020 20:08:52 +1100 Subject: [PATCH 083/442] update per comment --- .../PluginsManager.cs | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index bd80b3dfd..90f3277fb 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -138,34 +138,33 @@ namespace Flow.Launcher.Plugin.PluginsManager var results = resultsForUpdate .Select(x => new Result + { + Title = $"{x.Name} by {x.Author}", + SubTitle = $"Update from version {x.CurrentVersion} to {x.NewVersion}", + IcoPath = x.IcoPath, + Action = e => { - Title = $"{x.Name} by {x.Author}", - SubTitle = $"Update from version {x.CurrentVersion} to {x.NewVersion}", - IcoPath = x.IcoPath, - Action = e => + string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), + x.Name, x.Author, + Environment.NewLine, Environment.NewLine); + + if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) { - string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), - x.Name, x.Author, - Environment.NewLine, Environment.NewLine); + Uninstall(x.PluginExistingMetadata); - if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - Uninstall(x.PluginExistingMetadata); + var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, $"{x.Name}-{x.NewVersion}.zip"); + Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath); + Install(x.PluginNewUserPlugin, downloadToFilePath); - var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, $"{x.Name}-{x.NewVersion}.zip"); - Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath); - Install(x.PluginNewUserPlugin, downloadToFilePath); + Context.API.RestartApp(); - Context.API.RestartApp(); - - return true; - } - - return false; + return true; } - }) - .ToList(); + + return false; + } + }); return Search(results, uninstallSearch); } @@ -175,10 +174,10 @@ namespace Flow.Launcher.Plugin.PluginsManager return Context.API.GetAllPlugins().Any(x => x.Metadata.ID == id); } - internal List Search(List results, string searchName) + internal List Search(IEnumerable results, string searchName) { if (string.IsNullOrEmpty(searchName)) - return results; + return results.ToList(); return results .Where(x => @@ -211,8 +210,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return ShouldHideWindow; }, ContextData = x - }) - .ToList(); + }); return Search(results, searchName); } @@ -265,7 +263,7 @@ namespace Flow.Launcher.Plugin.PluginsManager var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty).TrimStart(); - var results= Context.API + var results = Context.API .GetAllPlugins() .Select(x => new Result @@ -291,8 +289,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return false; } - }) - .ToList(); + }); return Search(results, uninstallSearch); } From 3a6b6b721f697dd8fc163cf2e2590cfa187a01fd Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 20 Dec 2020 20:23:14 +1100 Subject: [PATCH 084/442] Flow.Launcher.Plugin version bump --- Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 0f6450d18..70013c274 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -14,10 +14,10 @@ - 1.3.0 - 1.3.0 - 1.3.0 - 1.3.0 + 1.3.1 + 1.3.1 + 1.3.1 + 1.3.1 Flow.Launcher.Plugin Flow-Launcher MIT From f693f1c6affba6f7ab466565b10ccde2eb07258c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 20 Dec 2020 17:25:14 +0800 Subject: [PATCH 085/442] use Array.empty instead of new [0] to avoid unused allocation --- Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 0f976cd58..e7a7c2bd7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -481,7 +481,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e); - return new Win32[0]; + return Array.Empty(); } #endif } From 110cf2a60710d0d3ad06264304859ba4a6030a7f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 20 Dec 2020 20:28:30 +1100 Subject: [PATCH 086/442] version bump --- SolutionAssemblyInfo.cs | 6 +++--- appveyor.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SolutionAssemblyInfo.cs b/SolutionAssemblyInfo.cs index 018084a66..ccbfef5d0 100644 --- a/SolutionAssemblyInfo.cs +++ b/SolutionAssemblyInfo.cs @@ -16,6 +16,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("1.5.0")] -[assembly: AssemblyFileVersion("1.5.0")] -[assembly: AssemblyInformationalVersion("1.5.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.6.0")] +[assembly: AssemblyFileVersion("1.6.0")] +[assembly: AssemblyInformationalVersion("1.6.0")] \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index f5841da3e..1f0937d6d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.5.0.{build}' +version: '1.6.0.{build}' init: - ps: | From cbfa3f354dc5c19cd84d1573853792db0b93cb41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 20 Dec 2020 17:44:46 +0800 Subject: [PATCH 087/442] Inform user when checking update fail or update fail, and wrap all code in one try catch instead of catch each exception seperately since they are all same kind of exception. Use using instead of manually dispose. --- Flow.Launcher.Core/Updater.cs | 110 ++++++++++++++-------------------- 1 file changed, 45 insertions(+), 65 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 20df23e40..3369526e0 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -29,89 +29,69 @@ namespace Flow.Launcher.Core GitHubRepository = gitHubRepository; } - public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) + public async Task UpdateApp(IPublicAPI api, bool silentUpdate = true) { - UpdateManager updateManager; - UpdateInfo newUpdateInfo; - - if (!silentUpdate) - api.ShowMsg("Please wait...", "Checking for new update"); - try { - updateManager = await GitHubUpdateManager(GitHubRepository); - } - catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) - { - Log.Exception($"|Updater.UpdateApp|Please check your connection and proxy settings to api.github.com.", e); - return; - } + UpdateInfo newUpdateInfo; + + if (!silentUpdate) + api.ShowMsg("Please wait...", "Checking for new update"); + + using var updateManager = await GitHubUpdateManager(GitHubRepository); + - try - { // UpdateApp CheckForUpdate will return value only if the app is squirrel installed newUpdateInfo = await updateManager.CheckForUpdate().NonNull(); - } - catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) - { - Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to api.github.com.", e); - updateManager.Dispose(); - return; - } - var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString()); - var currentVersion = Version.Parse(Constant.Version); + var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString()); + var currentVersion = Version.Parse(Constant.Version); - Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>"); + Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>"); + + if (newReleaseVersion <= currentVersion) + { + if (!silentUpdate) + MessageBox.Show("You already have the latest Flow Launcher version"); + updateManager.Dispose(); + return; + } - if (newReleaseVersion <= currentVersion) - { if (!silentUpdate) - MessageBox.Show("You already have the latest Flow Launcher version"); - updateManager.Dispose(); - return; - } + api.ShowMsg("Update found", "Updating..."); - if (!silentUpdate) - api.ShowMsg("Update found", "Updating..."); - - try - { await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply); + + await updateManager.ApplyReleases(newUpdateInfo); + + if (DataLocation.PortableDataLocationInUse()) + { + var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}"; + FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination); + if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination)) + MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " + + $"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}"); + } + else + { + await updateManager.CreateUninstallerRegistryEntry(); + } + + var newVersionTips = NewVersinoTips(newReleaseVersion.ToString()); + + Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}"); + + if (MessageBox.Show(newVersionTips, "New Update", MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + UpdateManager.RestartApp(Constant.ApplicationFileName); + } } catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); - updateManager.Dispose(); + api.ShowMsg("Update Fail!", "Check your connection and proxy settings to github-cloud.s3.amazonaws.com."); return; } - - await updateManager.ApplyReleases(newUpdateInfo); - - if (DataLocation.PortableDataLocationInUse()) - { - var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}"; - FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination); - if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination)) - MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " + - $"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}"); - } - else - { - await updateManager.CreateUninstallerRegistryEntry(); - } - - var newVersionTips = NewVersinoTips(newReleaseVersion.ToString()); - - Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}"); - - // always dispose UpdateManager - updateManager.Dispose(); - - if (MessageBox.Show(newVersionTips, "New Update", MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - UpdateManager.RestartApp(Constant.ApplicationFileName); - } } [UsedImplicitly] From adad5ae83bd8e036bcf93e2df174d4dbe7d2380e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 20 Dec 2020 21:10:55 +1100 Subject: [PATCH 088/442] add url website in Settings --- Flow.Launcher.Infrastructure/Constant.cs | 2 ++ Flow.Launcher/SettingWindow.xaml | 4 ++-- Flow.Launcher/ViewModel/SettingWindowViewModel.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index df1464048..3dba35f8d 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -35,5 +35,7 @@ namespace Flow.Launcher.Infrastructure public const string DefaultTheme = "Darker"; public const string Themes = "Themes"; + + public const string Website = "https://flow-launcher.github.io"; } } diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 32f9e9a6e..e47f0e779 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -429,8 +429,8 @@ - - + + diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 853925852..96ee5a052 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -450,7 +450,7 @@ namespace Flow.Launcher.ViewModel #region about - public string Github => _updater.GitHubRepository; + public string Website => Constant.Website; 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); From a9a62a5847b80e008ee00b6bce7b305e17822e88 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 21 Dec 2020 08:57:22 +1100 Subject: [PATCH 089/442] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0c08d8a0..5f3f7e1a7 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Windows may complain about security due to code not being signed, this will be c - Open flow's search window: Alt+Space is the default hotkey. - Open context menu: Ctrl+O/Shift+Enter. - Cancel/Return to previous screen: Esc. -- Install/Uninstall plugins: in the search window, type `wpm install/uninstall` + the plugin name. +- Install/Uninstall/Update plugins: in the search window, type `pm`/`pm uninstall`/`pm update` + the plugin name. - Saved user settings are located: - If using roaming: `%APPDATA%\FlowLauncher` - If using portable, by default: `%localappdata%\FlowLauncher\app-\UserData` From acd631be081c2fd044672c997eeb8998246b773d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 21 Dec 2020 19:09:18 +0800 Subject: [PATCH 090/442] Change wording --- Flow.Launcher.Core/Updater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 3369526e0..4e211c3ac 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -89,7 +89,7 @@ namespace Flow.Launcher.Core catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); - api.ShowMsg("Update Fail!", "Check your connection and proxy settings to github-cloud.s3.amazonaws.com."); + api.ShowMsg("Update Failed", "Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com."); return; } } From 85f5766022ec2282dd7cf2595c48785964e3433f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 21 Dec 2020 19:30:18 +0800 Subject: [PATCH 091/442] Optimize a few code --- Flow.Launcher.Infrastructure/Http/Http.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 34665217c..4ec4f887f 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -75,12 +75,12 @@ namespace Flow.Launcher.Infrastructure.Http } } - public async static Task Download([NotNull] string url, [NotNull] string filePath) + public static async Task Download([NotNull] string url, [NotNull] string filePath) { using var response = await client.GetAsync(url); if (response.StatusCode == HttpStatusCode.OK) { - using var fileStream = new FileStream(filePath, FileMode.CreateNew); + await using var fileStream = new FileStream(filePath, FileMode.CreateNew); await response.Content.CopyToAsync(fileStream); } else @@ -93,7 +93,7 @@ namespace Flow.Launcher.Infrastructure.Http { Log.Debug($"|Http.Get|Url <{url}>"); var response = await client.GetAsync(url); - using var stream = await response.Content.ReadAsStreamAsync(); + await using var stream = await response.Content.ReadAsStreamAsync(); using var reader = new StreamReader(stream, Encoding.GetEncoding(encoding)); var content = await reader.ReadToEndAsync(); if (response.StatusCode == HttpStatusCode.OK) From 96609f797e672021664c788f1d0acde1afec40ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 21 Dec 2020 19:42:50 +0800 Subject: [PATCH 092/442] Change the place of Wait in PluginManifest to make code more elegent --- .../Models/PluginsManifest.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs index 290221710..13a5ae2ca 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -10,21 +10,19 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models internal class PluginsManifest { internal List UserPlugins { get; private set; } + internal PluginsManifest() { - DownloadManifest(); + DownloadManifest().Wait(); } - private void DownloadManifest() + private async Task DownloadManifest() { var json = string.Empty; try { - var t = Task.Run( - async () => - json = await Http.Get("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json")); - - t.Wait(); + json = await Http.Get( + "https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json"); UserPlugins = JsonConvert.DeserializeObject>(json); } @@ -34,7 +32,6 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models UserPlugins = new List(); } - } } -} +} \ No newline at end of file From 5ab8c4faa3bc858af489096e3b8f4e9f9e290d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 21 Dec 2020 20:55:28 +0800 Subject: [PATCH 093/442] Update Proxy every time calling a http request method since the proxy setting won't update automatically without action --- Flow.Launcher.Infrastructure/Http/Http.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 4ec4f887f..a88d868a6 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -47,7 +47,14 @@ namespace Flow.Launcher.Infrastructure.Http } } - public static WebProxy WebProxy { get; private set; } = new WebProxy(); + private static WebProxy _proxy = new WebProxy(); + public static WebProxy WebProxy { + get + { + UpdateProxy(); + return _proxy; + } + } /// /// Update the Address of the Proxy to modify the client Proxy @@ -77,6 +84,7 @@ namespace Flow.Launcher.Infrastructure.Http public static async Task Download([NotNull] string url, [NotNull] string filePath) { + UpdateProxy(); using var response = await client.GetAsync(url); if (response.StatusCode == HttpStatusCode.OK) { @@ -91,6 +99,7 @@ namespace Flow.Launcher.Infrastructure.Http public static async Task Get([NotNull] string url, string encoding = "UTF-8") { + UpdateProxy(); Log.Debug($"|Http.Get|Url <{url}>"); var response = await client.GetAsync(url); await using var stream = await response.Content.ReadAsStreamAsync(); From 88fa862277eeff5a49058e72f3ec157ec817aa0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Tue, 22 Dec 2020 00:31:00 +0800 Subject: [PATCH 094/442] Use event triggered update method instead of checking Proxy every time doing Http request --- Flow.Launcher.Infrastructure/Http/Http.cs | 64 +++++++-------- .../UserSettings/HttpProxy.cs | 81 +++++++++++++++++-- 2 files changed, 105 insertions(+), 40 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index a88d868a6..8fe910c0c 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using System; +using System.ComponentModel; namespace Flow.Launcher.Infrastructure.Http { @@ -15,6 +16,7 @@ namespace Flow.Launcher.Infrastructure.Http private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko"; private static HttpClient client; + private static SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler() { UseProxy = true, @@ -31,60 +33,54 @@ namespace Flow.Launcher.Infrastructure.Http client = new HttpClient(socketsHttpHandler, false); client.DefaultRequestHeaders.Add("User-Agent", UserAgent); - } + private static HttpProxy proxy; + public static HttpProxy Proxy { - private get - { - return proxy; - } + private get { return proxy; } set { proxy = value; - UpdateProxy(); + proxy.PropertyChanged += UpdateProxy; } } - private static WebProxy _proxy = new WebProxy(); - public static WebProxy WebProxy { - get - { - UpdateProxy(); - return _proxy; - } + private static readonly WebProxy _proxy = new WebProxy(); + + public static WebProxy WebProxy + { + get { return _proxy; } } /// /// Update the Address of the Proxy to modify the client Proxy /// - public static void UpdateProxy() - // TODO: need test with a proxy + public static void UpdateProxy(ProxyProperty property) { - if (Proxy != null && Proxy.Enabled && !string.IsNullOrEmpty(Proxy.Server)) + (_proxy.Address, _proxy.Credentials) = property switch { - if (string.IsNullOrEmpty(Proxy.UserName) || string.IsNullOrEmpty(Proxy.Password)) + ProxyProperty.Enabled => (Proxy.Enabled) switch { - WebProxy.Address = new Uri($"http://{Proxy.Server}:{Proxy.Port}"); - WebProxy.Credentials = null; - } - else - { - WebProxy.Address = new Uri($"http://{Proxy.Server}:{Proxy.Port}"); - WebProxy.Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password); - } - } - else - { - WebProxy.Address = new WebProxy().Address; - WebProxy.Credentials = null; - } + true => Proxy.UserName switch + { + var userName when !string.IsNullOrEmpty(userName) => + (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), null), + _ => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), + new NetworkCredential(Proxy.UserName, Proxy.Password)) + }, + false => (null, null) + }, + ProxyProperty.Server => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), _proxy.Credentials), + ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), _proxy.Credentials), + ProxyProperty.UserName => (_proxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), + ProxyProperty.Password => (_proxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)) + }; } public static async Task Download([NotNull] string url, [NotNull] string filePath) { - UpdateProxy(); using var response = await client.GetAsync(url); if (response.StatusCode == HttpStatusCode.OK) { @@ -99,7 +95,6 @@ namespace Flow.Launcher.Infrastructure.Http public static async Task Get([NotNull] string url, string encoding = "UTF-8") { - UpdateProxy(); Log.Debug($"|Http.Get|Url <{url}>"); var response = await client.GetAsync(url); await using var stream = await response.Content.ReadAsStreamAsync(); @@ -111,7 +106,8 @@ namespace Flow.Launcher.Infrastructure.Http } else { - throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); + throw new HttpRequestException( + $"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); } } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/HttpProxy.cs b/Flow.Launcher.Infrastructure/UserSettings/HttpProxy.cs index c1b0c1dd7..213193526 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/HttpProxy.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/HttpProxy.cs @@ -1,11 +1,80 @@ -namespace Flow.Launcher.Infrastructure.UserSettings +using System.ComponentModel; + +namespace Flow.Launcher.Infrastructure.UserSettings { + public enum ProxyProperty + { + Enabled, + Server, + Port, + UserName, + Password + } + public class HttpProxy { - public bool Enabled { get; set; } = false; - public string Server { get; set; } - public int Port { get; set; } - public string UserName { get; set; } - public string Password { get; set; } + private bool _enabled = false; + private string _server; + private int _port; + private string _userName; + private string _password; + + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + OnPropertyChanged(ProxyProperty.Enabled); + } + } + + public string Server + { + get => _server; + set + { + _server = value; + OnPropertyChanged(ProxyProperty.Server); + } + } + + public int Port + { + get => _port; + set + { + _port = value; + OnPropertyChanged(ProxyProperty.Port); + } + } + + public string UserName + { + get => _userName; + set + { + _userName = value; + OnPropertyChanged(ProxyProperty.UserName); + } + } + + public string Password + { + get => _password; + set + { + _password = value; + OnPropertyChanged(ProxyProperty.Password); + } + } + + public delegate void ProxyPropertyChangedHandler(ProxyProperty property); + public event ProxyPropertyChangedHandler PropertyChanged; + + private void OnPropertyChanged(ProxyProperty property) + { + PropertyChanged?.Invoke(property); + } } } \ No newline at end of file From 968931e4a07246f0f15d3878dc50c535d193fdd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Tue, 22 Dec 2020 13:45:11 +0800 Subject: [PATCH 095/442] return if cancellation requested before changing _isQueryRunning state --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 25768a25e..a8a8ec1d7 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -474,6 +474,8 @@ namespace Flow.Launcher.ViewModel // nothing to do here } + if (currentCancellationToken.IsCancellationRequested) + return; // this should happen once after all queries are done so progress bar should continue // until the end of all querying From b7a0ada60b72b2d5c619e455d1b615ff713776d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Tue, 22 Dec 2020 21:53:59 +0800 Subject: [PATCH 096/442] Add Mapping to original string after translation. Not sure about the performance, but seems satisfying. It requires at most n times loop (n: number of translated charater) mapping once. --- .../PinyinAlphabet.cs | 81 +++++++++++++++---- Flow.Launcher.Infrastructure/StringMatcher.cs | 78 +++++++++--------- 2 files changed, 108 insertions(+), 51 deletions(-) diff --git a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs index 80fd12820..4f1aedd4a 100644 --- a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs +++ b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs @@ -1,21 +1,77 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Text; using JetBrains.Annotations; using Flow.Launcher.Infrastructure.UserSettings; +using Microsoft.AspNetCore.Localization; using ToolGood.Words.Pinyin; namespace Flow.Launcher.Infrastructure { + public class TranslationMapping + { + private bool constructed; + + private List originalIndexs = new List(); + private List translatedIndexs = new List(); + private int translaedLength = 0; + + public void AddNewIndex(int originalIndex, int translatedIndex, int length) + { + if (constructed) + throw new InvalidOperationException("Mapping shouldn't be changed after constructed"); + + originalIndexs.Add(originalIndex); + translatedIndexs.Add(translatedIndex); + translatedIndexs.Add(translatedIndex + length); + translaedLength += length - 1; + } + + public int? MapToOriginalIndex(int translatedIndex) + { + if (translatedIndex > translatedIndexs.Last()) + return translatedIndex - translaedLength - 1; + + for (var i = 0; i < originalIndexs.Count; i++) + { + if (translatedIndex >= translatedIndexs[i * 2] && translatedIndex < translatedIndexs[i * 2 + 1]) + return originalIndexs[i]; + if (translatedIndex < translatedIndexs[i * 2]) + { + int indexDiff = 0; + for (int j = 0; j < i; j++) + { + indexDiff += translatedIndexs[i * 2 + 1] - translatedIndexs[i * 2] - 1; + } + + return translatedIndex - indexDiff; + } + } + + return translatedIndex; + } + + public void endConstruct() + { + if (constructed) + throw new InvalidOperationException("Mapping has already been constructed"); + constructed = true; + } + } + public interface IAlphabet { - string Translate(string stringToTranslate); + public (string translation, TranslationMapping map) Translate(string stringToTranslate); } public class PinyinAlphabet : IAlphabet { - private ConcurrentDictionary _pinyinCache = new ConcurrentDictionary(); + private ConcurrentDictionary _pinyinCache = + new ConcurrentDictionary(); + + private Settings _settings; public void Initialize([NotNull] Settings settings) @@ -23,7 +79,7 @@ namespace Flow.Launcher.Infrastructure _settings = settings ?? throw new ArgumentNullException(nameof(settings)); } - public string Translate(string content) + public (string translation, TranslationMapping map) Translate(string content) { if (_settings.ShouldUsePinyin) { @@ -34,14 +90,7 @@ namespace Flow.Launcher.Infrastructure var resultList = WordsHelper.GetPinyinList(content); StringBuilder resultBuilder = new StringBuilder(); - - for (int i = 0; i < resultList.Length; i++) - { - if (content[i] >= 0x3400 && content[i] <= 0x9FD5) - resultBuilder.Append(resultList[i].First()); - } - - resultBuilder.Append(' '); + TranslationMapping map = new TranslationMapping(); bool pre = false; @@ -49,6 +98,7 @@ namespace Flow.Launcher.Infrastructure { if (content[i] >= 0x3400 && content[i] <= 0x9FD5) { + map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1); resultBuilder.Append(' '); resultBuilder.Append(resultList[i]); pre = true; @@ -60,15 +110,18 @@ namespace Flow.Launcher.Infrastructure pre = false; resultBuilder.Append(' '); } + resultBuilder.Append(resultList[i]); } } - return _pinyinCache[content] = resultBuilder.ToString(); + map.endConstruct(); + + return _pinyinCache[content] = (resultBuilder.ToString(), map); } else { - return content; + return (content, null); } } else @@ -78,7 +131,7 @@ namespace Flow.Launcher.Infrastructure } else { - return content; + return (content, null); } } } diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 96391f1d6..e885798b7 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -44,22 +44,12 @@ namespace Flow.Launcher.Infrastructure /// public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt) { - if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult(false, UserSettingSearchPrecision); + if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) + return new MatchResult(false, UserSettingSearchPrecision); query = query.Trim(); - - stringToCompare = _alphabet?.Translate(stringToCompare) ?? stringToCompare; - - // This also can be done by spliting the query - - //(var spaceSplit, var upperSplit) = stringToCompare switch - //{ - // string s when s.Contains(' ') => (s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(w => w.First()), - // default(IEnumerable)), - // string s when s.Any(c => char.IsUpper(c)) && s.Any(c => char.IsLower(c)) => - // (null, Regex.Split(s, @"(? w.First())), - // _ => ((IEnumerable)null, (IEnumerable)null) - //}; + TranslationMapping map; + (stringToCompare, map) = _alphabet?.Translate(stringToCompare) ?? (stringToCompare, null); var currentQueryIndex = 0; var acronymMatchData = new List(); @@ -75,19 +65,21 @@ namespace Flow.Launcher.Infrastructure switch (stringToCompare[compareIndex]) { - case char c when (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex])) - || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) - || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]) - || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): - acronymMatchData.Add(compareIndex); + case var c when (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == + char.ToLower(stringToCompare[compareIndex])) + || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) + || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == + queryWithoutCase[currentQueryIndex]) + || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): + acronymMatchData.Add(map?.MapToOriginalIndex(compareIndex) ?? compareIndex); currentQueryIndex++; continue; - case char c when char.IsWhiteSpace(c): + case var c when char.IsWhiteSpace(c): compareIndex++; acronymScore -= 10; break; - case char c when char.IsUpper(c) || char.IsNumber(c): + case var c when char.IsUpper(c) || char.IsNumber(c): acronymScore -= 10; break; } @@ -99,7 +91,7 @@ namespace Flow.Launcher.Infrastructure var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; - var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var querySubstrings = queryWithoutCase.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries); int currentQuerySubstringIndex = 0; var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; var currentQuerySubstringCharacterIndex = 0; @@ -114,9 +106,10 @@ namespace Flow.Launcher.Infrastructure var indexList = new List(); List spaceIndices = new List(); - for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) + for (var compareStringIndex = 0; + compareStringIndex < fullStringToCompareWithoutCase.Length; + compareStringIndex++) { - // To maintain a list of indices which correspond to spaces in the string to compare // To populate the list only for the first query substring if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0) @@ -124,7 +117,8 @@ namespace Flow.Launcher.Infrastructure spaceIndices.Add(compareStringIndex); } - if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex]) + if (fullStringToCompareWithoutCase[compareStringIndex] != + currentQuerySubstring[currentQuerySubstringCharacterIndex]) { matchFoundInPreviousLoop = false; continue; @@ -148,14 +142,16 @@ namespace Flow.Launcher.Infrastructure // 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)) + 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); + indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, + firstMatchIndexInWord, indexList); } } @@ -168,11 +164,13 @@ namespace Flow.Launcher.Infrastructure if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length) { // if any of the substrings was not matched then consider as all are not matched - allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString; + allSubstringsContainedInCompareString = + matchFoundInPreviousLoop && allSubstringsContainedInCompareString; currentQuerySubstringIndex++; - allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length); + allQuerySubstringsMatched = + AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length); if (allQuerySubstringsMatched) break; @@ -182,13 +180,16 @@ namespace Flow.Launcher.Infrastructure } } + // proceed to calculate score if every char or substring without whitespaces matched if (allQuerySubstringsMatched) { var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex); - var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); + var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, + lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); - return new MatchResult(true, UserSettingSearchPrecision, indexList, score); + var resultList = indexList.Distinct().Select(x => map?.MapToOriginalIndex(x) ?? x).ToList(); + return new MatchResult(true, UserSettingSearchPrecision, resultList, score); } return new MatchResult(false, UserSettingSearchPrecision); @@ -203,14 +204,15 @@ namespace Flow.Launcher.Infrastructure } else { - int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault(); + int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)) + .FirstOrDefault(item => firstMatchIndex > item); int closestSpaceIndex = ind ?? -1; return closestSpaceIndex; } } private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex, - string fullStringToCompareWithoutCase, string currentQuerySubstring) + string fullStringToCompareWithoutCase, string currentQuerySubstring) { var allMatch = true; for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++) @@ -225,7 +227,8 @@ namespace Flow.Launcher.Infrastructure return allMatch; } - private static List GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List indexList) + private static List GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, + int firstMatchIndexInWord, List indexList) { var updatedList = new List(); @@ -246,7 +249,8 @@ namespace Flow.Launcher.Infrastructure return currentQuerySubstringIndex >= querySubstringsLength; } - private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString) + 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, @@ -341,7 +345,7 @@ namespace Flow.Launcher.Infrastructure private bool IsSearchPrecisionScoreMet(int rawScore) { - return rawScore >= (int)SearchPrecision; + return rawScore >= (int) SearchPrecision; } private int ScoreAfterSearchPrecisionFilter(int rawScore) @@ -354,4 +358,4 @@ namespace Flow.Launcher.Infrastructure { public bool IgnoreCase { get; set; } = true; } -} +} \ No newline at end of file From 2c9f4149b7cbd0d86fd46e07f2fa2509917e67f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Tue, 22 Dec 2020 22:58:27 +0800 Subject: [PATCH 097/442] optimize use --- Flow.Launcher.Infrastructure/StringMatcher.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index e885798b7..22334c4bd 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -71,7 +71,7 @@ namespace Flow.Launcher.Infrastructure || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]) || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): - acronymMatchData.Add(map?.MapToOriginalIndex(compareIndex) ?? compareIndex); + acronymMatchData.Add(compareIndex); currentQueryIndex++; continue; @@ -86,7 +86,10 @@ namespace Flow.Launcher.Infrastructure } if (acronymMatchData.Count == query.Length && acronymScore >= 60) + { + acronymMatchData = acronymMatchData.Select(x => map?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); + } var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; @@ -188,7 +191,7 @@ namespace Flow.Launcher.Infrastructure var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); - var resultList = indexList.Distinct().Select(x => map?.MapToOriginalIndex(x) ?? x).ToList(); + var resultList = indexList.Select(x => map?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); return new MatchResult(true, UserSettingSearchPrecision, resultList, score); } From 93486783e5fdb5f5e14abc1df1b4c5e8adccb1eb Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 23 Dec 2020 06:28:59 +1100 Subject: [PATCH 098/442] update plugin repo url --- Flow.Launcher/ViewModel/SettingWindowViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 96ee5a052..c122f8037 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -213,7 +213,7 @@ namespace Flow.Launcher.ViewModel #region plugin - public static string Plugin => "http://www.wox.one/plugin"; + public static string Plugin => @"https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"; public PluginViewModel SelectedPlugin { get; set; } public IList PluginViewModels From 75b99415eb90f4ee7f8c5a30fedef4f785480cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 26 Dec 2020 00:29:35 +0800 Subject: [PATCH 099/442] Use Binary Search instead of Linear search to reduce time complexity. Add Key Property for debugging. --- .../PinyinAlphabet.cs | 78 +++++++++++++++---- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs index 4f1aedd4a..be3c58f66 100644 --- a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs +++ b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs @@ -18,6 +18,13 @@ namespace Flow.Launcher.Infrastructure private List translatedIndexs = new List(); private int translaedLength = 0; + public string key { get; private set; } + + public void setKey(string key) + { + this.key = key; + } + public void AddNewIndex(int originalIndex, int translatedIndex, int length) { if (constructed) @@ -29,28 +36,64 @@ namespace Flow.Launcher.Infrastructure translaedLength += length - 1; } - public int? MapToOriginalIndex(int translatedIndex) + public int MapToOriginalIndex(int translatedIndex) { if (translatedIndex > translatedIndexs.Last()) return translatedIndex - translaedLength - 1; - - for (var i = 0; i < originalIndexs.Count; i++) - { - if (translatedIndex >= translatedIndexs[i * 2] && translatedIndex < translatedIndexs[i * 2 + 1]) - return originalIndexs[i]; - if (translatedIndex < translatedIndexs[i * 2]) - { - int indexDiff = 0; - for (int j = 0; j < i; j++) - { - indexDiff += translatedIndexs[i * 2 + 1] - translatedIndexs[i * 2] - 1; - } - return translatedIndex - indexDiff; + int lowerBound = 0; + int upperBound = originalIndexs.Count - 1; + + int count = 0; + + + // Corner case handle + if (translatedIndex < translatedIndexs[0]) + return translatedIndex; + if (translatedIndex > translatedIndexs.Last()) + { + int indexDef = 0; + for (int k = 0; k < originalIndexs.Count; k++) + { + indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2]; } + + return translatedIndex - indexDef - 1; } - return translatedIndex; + // Binary Search with Range + for (int i = originalIndexs.Count / 2;; count++) + { + if (translatedIndex < translatedIndexs[i * 2]) + { + // move to lower middle + upperBound = i; + i = (i + lowerBound) / 2; + } + else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1) + { + lowerBound = i; + // move to upper middle + // due to floor of integer division, move one up on corner case + i = (i + upperBound + 1) / 2; + } + else + return originalIndexs[i]; + + if (upperBound - lowerBound <= 1 && + translatedIndex > translatedIndexs[lowerBound * 2 + 1] && + translatedIndex < translatedIndexs[upperBound * 2]) + { + int indexDef = 0; + + for (int j = 0; j < upperBound; j++) + { + indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2]; + } + + return translatedIndex - indexDef - 1; + } + } } public void endConstruct() @@ -117,7 +160,10 @@ namespace Flow.Launcher.Infrastructure map.endConstruct(); - return _pinyinCache[content] = (resultBuilder.ToString(), map); + var key = resultBuilder.ToString(); + map.setKey(key); + + return _pinyinCache[content] = (key, map); } else { From 7ceb08071c6086f39911f1ff2c920e160267d41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 27 Dec 2020 20:16:20 +0800 Subject: [PATCH 100/442] Use inner loop to evaluate acronym match (Big Change) Don't end loop before acronym match end since if acronym match exist, we will use that one. --- Flow.Launcher.Infrastructure/StringMatcher.cs | 99 ++++++++++++------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 22334c4bd..7ade76cdf 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -51,46 +51,13 @@ namespace Flow.Launcher.Infrastructure TranslationMapping map; (stringToCompare, map) = _alphabet?.Translate(stringToCompare) ?? (stringToCompare, null); - var currentQueryIndex = 0; + var currentAcronymQueryIndex = 0; var acronymMatchData = new List(); var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; + // preset acronymScore int acronymScore = 100; - for (int compareIndex = 0; compareIndex < stringToCompare.Length; compareIndex++) - { - if (currentQueryIndex >= queryWithoutCase.Length) - break; - - - switch (stringToCompare[compareIndex]) - { - case var c when (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == - char.ToLower(stringToCompare[compareIndex])) - || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) - || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == - queryWithoutCase[currentQueryIndex]) - || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): - acronymMatchData.Add(compareIndex); - currentQueryIndex++; - continue; - - case var c when char.IsWhiteSpace(c): - compareIndex++; - acronymScore -= 10; - break; - case var c when char.IsUpper(c) || char.IsNumber(c): - acronymScore -= 10; - break; - } - } - - if (acronymMatchData.Count == query.Length && acronymScore >= 60) - { - acronymMatchData = acronymMatchData.Select(x => map?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); - return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); - } - var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; @@ -109,24 +76,72 @@ namespace Flow.Launcher.Infrastructure var indexList = new List(); List spaceIndices = new List(); + bool spaceMet = false; + for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) { + if (currentAcronymQueryIndex >= queryWithoutCase.Length + || allQuerySubstringsMatched && acronymScore < (int) UserSettingSearchPrecision) + break; + + // To maintain a list of indices which correspond to spaces in the string to compare // To populate the list only for the first query substring - if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0) + if (fullStringToCompareWithoutCase[compareStringIndex] == ' ' && currentQuerySubstringIndex == 0) { spaceIndices.Add(compareStringIndex); } - if (fullStringToCompareWithoutCase[compareStringIndex] != + // Acronym check + if (char.IsUpper(stringToCompare[compareStringIndex]) || + char.IsNumber(stringToCompare[compareStringIndex]) || + char.IsWhiteSpace(stringToCompare[compareStringIndex]) || + spaceMet) + { + if (fullStringToCompareWithoutCase[compareStringIndex] == + queryWithoutCase[currentAcronymQueryIndex]) + { + currentAcronymQueryIndex++; + + if (!spaceMet) + { + char currentCompareChar = stringToCompare[compareStringIndex]; + spaceMet = char.IsWhiteSpace(currentCompareChar); + // if is space, no need to check whether upper or digit, though insignificant + if (!spaceMet && compareStringIndex == 0 || char.IsUpper(currentCompareChar) || + char.IsDigit(currentCompareChar)) + { + acronymMatchData.Add(compareStringIndex); + } + } + else if (!(spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex]))) + { + acronymMatchData.Add(compareStringIndex); + } + } + else + { + spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex]); + // Acronym Penalty + if (!spaceMet) + { + acronymScore -= 10; + } + } + } + // Acronym end + + if (allQuerySubstringsMatched || fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex]) { matchFoundInPreviousLoop = false; + continue; } + if (firstMatchIndex < 0) { // first matched char will become the start of the compared string @@ -174,8 +189,9 @@ namespace Flow.Launcher.Infrastructure allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length); + if (allQuerySubstringsMatched) - break; + continue; // otherwise move to the next query substring currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; @@ -183,6 +199,12 @@ namespace Flow.Launcher.Infrastructure } } + // return acronym Match if possible + if (acronymMatchData.Count == query.Length && acronymScore >= (int) UserSettingSearchPrecision) + { + acronymMatchData = acronymMatchData.Select(x => map?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); + return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); + } // proceed to calculate score if every char or substring without whitespaces matched if (allQuerySubstringsMatched) @@ -249,6 +271,7 @@ namespace Flow.Launcher.Infrastructure private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength) { + // Acronym won't utilize the substring to match return currentQuerySubstringIndex >= querySubstringsLength; } From 34f51927cf5955d49174a45cbb0eee60802b7466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 27 Dec 2020 21:13:25 +0800 Subject: [PATCH 101/442] 1. Move PluginsManager constuction to Init(). 2. Return HotKeys list when query is like "pm *" --- Flow.Launcher.Infrastructure/Http/Http.cs | 2 +- .../Main.cs | 34 ++- .../PluginsManager.cs | 250 ++++++++++-------- .../Settings.cs | 46 +++- 4 files changed, 201 insertions(+), 131 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 1d5f240e1..2d798ec11 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -59,7 +59,7 @@ namespace Flow.Launcher.Infrastructure.Http Log.Debug($"|Http.Get|Url <{url}>"); var request = WebRequest.CreateHttp(url); request.Method = "GET"; - request.Timeout = 1000; + request.Timeout = 6000; request.Proxy = WebProxy(); request.UserAgent = UserAgent; var response = await request.GetResponseAsync() as HttpWebResponse; diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 43f92e7b9..6cf9e1608 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -3,13 +3,15 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.PluginsManager.ViewModels; using Flow.Launcher.Plugin.PluginsManager.Views; using System.Collections.Generic; +using System.Linq; using System.Windows.Controls; +using Flow.Launcher.Infrastructure; namespace Flow.Launcher.Plugin.PluginsManager { public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n { - internal PluginInitContext Context { get; set; } + internal static PluginInitContext Context { get; set; } internal Settings Settings; @@ -17,6 +19,8 @@ namespace Flow.Launcher.Plugin.PluginsManager private IContextMenu contextMenu; + internal PluginsManager pluginManager; + public Control CreateSettingPanel() { return new PluginsManagerSettings(viewModel); @@ -28,6 +32,7 @@ namespace Flow.Launcher.Plugin.PluginsManager viewModel = new SettingsViewModel(context); Settings = viewModel.Settings; contextMenu = new ContextMenu(Context, Settings); + pluginManager = new PluginsManager(Context, Settings); } public List LoadContextMenus(Result selectedResult) @@ -38,18 +43,21 @@ namespace Flow.Launcher.Plugin.PluginsManager public List Query(Query query) { var search = query.Search.ToLower(); + + if (string.IsNullOrWhiteSpace(search)) + return Settings.HotKeys; - var pluginManager = new PluginsManager(Context, Settings); - - if (!string.IsNullOrEmpty(search) - && ($"{Settings.HotkeyUninstall} ".StartsWith(search) || search.StartsWith($"{Settings.HotkeyUninstall} "))) - return pluginManager.RequestUninstall(search); - - if (!string.IsNullOrEmpty(search) - && ($"{Settings.HotkeyUpdate} ".StartsWith(search) || search.StartsWith($"{Settings.HotkeyUpdate} "))) - return pluginManager.RequestUpdate(search); - - return pluginManager.RequestInstallOrUpdate(search); + return search switch + { + var s when s.StartsWith(Settings.HotKeyInstall) => pluginManager.RequestInstallOrUpdate(s), + var s when s.StartsWith(Settings.HotkeyUninstall) => pluginManager.RequestUninstall(s), + var s when s.StartsWith(Settings.HotkeyUpdate) => pluginManager.RequestUpdate(s), + _ => Settings.HotKeys.Where(hotkey => + { + hotkey.Score = StringMatcher.FuzzySearch(search, hotkey.Title).Score; + return hotkey.Score > 0; + }).ToList() + }; } public void Save() @@ -67,4 +75,4 @@ namespace Flow.Launcher.Plugin.PluginsManager return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); } } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 90f3277fb..23d2756ae 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -20,7 +20,8 @@ namespace Flow.Launcher.Plugin.PluginsManager private Settings Settings { get; set; } private bool shouldHideWindow = true; - private bool ShouldHideWindow + + private bool ShouldHideWindow { set { shouldHideWindow = value; } get @@ -42,18 +43,21 @@ namespace Flow.Launcher.Plugin.PluginsManager Context = context; Settings = settings; } + internal void InstallOrUpdate(UserPlugin plugin) { if (PluginExists(plugin.ID)) { - if (Context.API.GetAllPlugins().Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version != plugin.Version)) + if (Context.API.GetAllPlugins() + .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version != plugin.Version)) { if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), - Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - Context - .API - .ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.HotkeyUpdate} {plugin.Name}"); + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + Context + .API + .ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.HotkeyUpdate} {plugin.Name}"); Application.Current.MainWindow.Show(); shouldHideWindow = false; @@ -66,10 +70,11 @@ namespace Flow.Launcher.Plugin.PluginsManager } var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"), - plugin.Name, plugin.Author, - Environment.NewLine, Environment.NewLine); + plugin.Name, plugin.Author, + Environment.NewLine, Environment.NewLine); - if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"), MessageBoxButton.YesNo) == MessageBoxResult.No) + if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"), + MessageBoxButton.YesNo) == MessageBoxResult.No) return; var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}.zip"); @@ -77,30 +82,34 @@ namespace Flow.Launcher.Plugin.PluginsManager try { Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); + Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); Http.Download(plugin.UrlDownload, filePath); Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - Context.API.GetTranslation("plugin_pluginsmanager_download_success")); + Context.API.GetTranslation("plugin_pluginsmanager_download_success")); } catch (Exception e) { Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - Context.API.GetTranslation("plugin_pluginsmanager_download_success")); + Context.API.GetTranslation("plugin_pluginsmanager_download_success")); Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); } - Application.Current.Dispatcher.Invoke(() => { Install(plugin, filePath); Context.API.RestartApp(); }); + Application.Current.Dispatcher.Invoke(() => + { + Install(plugin, filePath); + Context.API.RestartApp(); + }); } internal List RequestUpdate(string search) { var autocompletedResults = AutoCompleteReturnAllResults(search, - Settings.HotkeyUpdate, - "Update", - "Select a plugin to update"); + Settings.HotkeyUpdate, + "Update", + "Select a plugin to update"); if (autocompletedResults.Any()) return autocompletedResults; @@ -108,63 +117,68 @@ namespace Flow.Launcher.Plugin.PluginsManager var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty).TrimStart(); - var resultsForUpdate = - from existingPlugin in Context.API.GetAllPlugins() - join pluginFromManifest in pluginsManifest.UserPlugins - on existingPlugin.Metadata.ID equals pluginFromManifest.ID - where existingPlugin.Metadata.Version != pluginFromManifest.Version - select - new - { - pluginFromManifest.Name, - pluginFromManifest.Author, - CurrentVersion = existingPlugin.Metadata.Version, - NewVersion = pluginFromManifest.Version, - existingPlugin.Metadata.IcoPath, - PluginExistingMetadata = existingPlugin.Metadata, - PluginNewUserPlugin = pluginFromManifest - }; + var resultsForUpdate = + from existingPlugin in Context.API.GetAllPlugins() + join pluginFromManifest in pluginsManifest.UserPlugins + on existingPlugin.Metadata.ID equals pluginFromManifest.ID + where existingPlugin.Metadata.Version != pluginFromManifest.Version + select + new + { + pluginFromManifest.Name, + pluginFromManifest.Author, + CurrentVersion = existingPlugin.Metadata.Version, + NewVersion = pluginFromManifest.Version, + existingPlugin.Metadata.IcoPath, + PluginExistingMetadata = existingPlugin.Metadata, + PluginNewUserPlugin = pluginFromManifest + }; if (!resultsForUpdate.Any()) - return new List { - new Result - { - Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"), - SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"), - IcoPath = icoPath - }}; + return new List + { + new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"), + IcoPath = icoPath + } + }; var results = resultsForUpdate - .Select(x => - new Result - { - Title = $"{x.Name} by {x.Author}", - SubTitle = $"Update from version {x.CurrentVersion} to {x.NewVersion}", - IcoPath = x.IcoPath, - Action = e => - { - string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), - x.Name, x.Author, - Environment.NewLine, Environment.NewLine); + .Select(x => + new Result + { + Title = $"{x.Name} by {x.Author}", + SubTitle = $"Update from version {x.CurrentVersion} to {x.NewVersion}", + IcoPath = x.IcoPath, + Action = e => + { + string message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), + x.Name, x.Author, + Environment.NewLine, Environment.NewLine); - if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - Uninstall(x.PluginExistingMetadata); + if (MessageBox.Show(message, + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + Uninstall(x.PluginExistingMetadata); - var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, $"{x.Name}-{x.NewVersion}.zip"); - Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath); - Install(x.PluginNewUserPlugin, downloadToFilePath); + var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, + $"{x.Name}-{x.NewVersion}.zip"); + Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath); + Install(x.PluginNewUserPlugin, downloadToFilePath); - Context.API.RestartApp(); + Context.API.RestartApp(); - return true; - } + return true; + } - return false; - } - }); + return false; + } + }); return Search(results, uninstallSearch); } @@ -180,37 +194,37 @@ namespace Flow.Launcher.Plugin.PluginsManager return results.ToList(); return results - .Where(x => - { - var matchResult = StringMatcher.FuzzySearch(searchName, x.Title); - if (matchResult.IsSearchPrecisionScoreMet()) - x.Score = matchResult.Score; + .Where(x => + { + var matchResult = StringMatcher.FuzzySearch(searchName, x.Title); + if (matchResult.IsSearchPrecisionScoreMet()) + x.Score = matchResult.Score; - return matchResult.IsSearchPrecisionScoreMet(); - }) - .ToList(); + return matchResult.IsSearchPrecisionScoreMet(); + }) + .ToList(); } internal List RequestInstallOrUpdate(string searchName) { var results = pluginsManifest - .UserPlugins - .Select(x => - new Result - { - Title = $"{x.Name} by {x.Author}", - SubTitle = x.Description, - IcoPath = icoPath, - Action = e => + .UserPlugins + .Select(x => + new Result { - Application.Current.MainWindow.Hide(); - InstallOrUpdate(x); + Title = $"{x.Name} by {x.Author}", + SubTitle = x.Description, + IcoPath = icoPath, + Action = e => + { + Application.Current.MainWindow.Hide(); + InstallOrUpdate(x); - return ShouldHideWindow; - }, - ContextData = x - }); + return ShouldHideWindow; + }, + ContextData = x + }); return Search(results, searchName); } @@ -253,10 +267,10 @@ namespace Flow.Launcher.Plugin.PluginsManager internal List RequestUninstall(string search) { - var autocompletedResults = AutoCompleteReturnAllResults(search, - Settings.HotkeyUninstall, - "Uninstall", - "Select a plugin to uninstall"); + var autocompletedResults = AutoCompleteReturnAllResults(search, + Settings.HotkeyUninstall, + "Uninstall", + "Select a plugin to uninstall"); if (autocompletedResults.Any()) return autocompletedResults; @@ -264,32 +278,34 @@ namespace Flow.Launcher.Plugin.PluginsManager var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty).TrimStart(); var results = Context.API - .GetAllPlugins() - .Select(x => - new Result - { - Title = $"{x.Metadata.Name} by {x.Metadata.Author}", - SubTitle = x.Metadata.Description, - IcoPath = x.Metadata.IcoPath, - Action = e => - { - string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), - x.Metadata.Name, x.Metadata.Author, - Environment.NewLine, Environment.NewLine); + .GetAllPlugins() + .Select(x => + new Result + { + Title = $"{x.Metadata.Name} by {x.Metadata.Author}", + SubTitle = x.Metadata.Description, + IcoPath = x.Metadata.IcoPath, + Action = e => + { + string message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), + x.Metadata.Name, x.Metadata.Author, + Environment.NewLine, Environment.NewLine); - if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - Application.Current.MainWindow.Hide(); - Uninstall(x.Metadata); - Context.API.RestartApp(); + if (MessageBox.Show(message, + Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + Application.Current.MainWindow.Hide(); + Uninstall(x.Metadata); + Context.API.RestartApp(); - return true; - } + return true; + } - return false; - } - }); + return false; + } + }); return Search(results, uninstallSearch); } @@ -300,6 +316,7 @@ namespace Flow.Launcher.Plugin.PluginsManager using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); } + private List AutoCompleteReturnAllResults(string search, string hotkey, string title, string subtitle) { if (!string.IsNullOrEmpty(search) @@ -317,8 +334,9 @@ namespace Flow.Launcher.Plugin.PluginsManager Action = e => { Context - .API - .ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {hotkey} "); + .API + .ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {hotkey} "); return false; } @@ -329,4 +347,4 @@ namespace Flow.Launcher.Plugin.PluginsManager return new List(); } } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index e2e8d22e5..5fc3916c0 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -6,8 +6,52 @@ namespace Flow.Launcher.Plugin.PluginsManager { internal class Settings { + internal string HotKeyInstall { get; set; } = "install"; internal string HotkeyUninstall { get; set; } = "uninstall"; internal string HotkeyUpdate { get; set; } = "update"; + + internal readonly string icoPath = "Images\\pluginsmanager.png"; + + + internal List HotKeys + { + get + { + return new List() + { + new Result() + { + Title = HotKeyInstall, + IcoPath = icoPath, + Action = _ => + { + Main.Context.API.ChangeQuery("pm install "); + return false; + } + }, + new Result() + { + Title = HotkeyUninstall, + IcoPath = icoPath, + Action = _ => + { + Main.Context.API.ChangeQuery("pm uninstall "); + return false; + } + }, + new Result() + { + Title = HotkeyUpdate, + IcoPath = icoPath, + Action = _ => + { + Main.Context.API.ChangeQuery("pm update "); + return false; + } + } + }; + } + } } -} +} \ No newline at end of file From deaac1c7e48ff6ef370a4127247fc22cb94a8f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 27 Dec 2020 22:33:12 +0800 Subject: [PATCH 102/442] replace install from search keyword --- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 23d2756ae..bd998514a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -207,6 +207,8 @@ namespace Flow.Launcher.Plugin.PluginsManager internal List RequestInstallOrUpdate(string searchName) { + var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty).Trim(); + var results = pluginsManifest .UserPlugins @@ -226,7 +228,7 @@ namespace Flow.Launcher.Plugin.PluginsManager ContextData = x }); - return Search(results, searchName); + return Search(results, searchNameWithoutKeyword); } private void Install(UserPlugin plugin, string downloadedFilePath) From 5a0358718cd9b76b71da0c885c853f39e7e09b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 28 Dec 2020 23:06:47 +0800 Subject: [PATCH 103/442] Update PluginManifest if last update time is 12 hours ago --- .../Flow.Launcher.Plugin.PluginsManager/Main.cs | 16 +++++++++++++++- .../Models/PluginsManifest.cs | 10 +++------- .../PluginsManager.cs | 8 +++++++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 6cf9e1608..9aa024fc8 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Controls; using Flow.Launcher.Infrastructure; +using System; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin.PluginsManager { @@ -21,6 +23,8 @@ namespace Flow.Launcher.Plugin.PluginsManager internal PluginsManager pluginManager; + internal DateTime _lastUpdateTime; + public Control CreateSettingPanel() { return new PluginsManagerSettings(viewModel); @@ -33,6 +37,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Settings = viewModel.Settings; contextMenu = new ContextMenu(Context, Settings); pluginManager = new PluginsManager(Context, Settings); + _lastUpdateTime = DateTime.Now; } public List LoadContextMenus(Result selectedResult) @@ -43,10 +48,19 @@ namespace Flow.Launcher.Plugin.PluginsManager public List Query(Query query) { var search = query.Search.ToLower(); - + if (string.IsNullOrWhiteSpace(search)) return Settings.HotKeys; + if ((DateTime.Now - _lastUpdateTime).TotalSeconds > 43200) // 12 hours + { + Task.Run(async () => + { + await pluginManager.UpdateManifest(); + _lastUpdateTime = DateTime.Now; + }); + } + return search switch { var s when s.StartsWith(Settings.HotKeyInstall) => pluginManager.RequestInstallOrUpdate(s), diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs index 290221710..1c31432b2 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -12,19 +12,15 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models internal List UserPlugins { get; private set; } internal PluginsManifest() { - DownloadManifest(); + Task.Run(() => DownloadManifest()).Wait(); } - private void DownloadManifest() + internal async Task DownloadManifest() { var json = string.Empty; try { - var t = Task.Run( - async () => - json = await Http.Get("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json")); - - t.Wait(); + json = await Http.Get("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json"); UserPlugins = JsonConvert.DeserializeObject>(json); } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index bd998514a..c6f127387 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -7,13 +7,14 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Windows; namespace Flow.Launcher.Plugin.PluginsManager { internal class PluginsManager { - private readonly PluginsManifest pluginsManifest; + private PluginsManifest pluginsManifest; private PluginInitContext Context { get; set; } @@ -44,6 +45,11 @@ namespace Flow.Launcher.Plugin.PluginsManager Settings = settings; } + internal async Task UpdateManifest() + { + await pluginsManifest.DownloadManifest(); + } + internal void InstallOrUpdate(UserPlugin plugin) { if (PluginExists(plugin.ID)) From 00457ddc29aad8088eac5439d0be10f2368a82f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 12:08:41 +0800 Subject: [PATCH 104/442] change totalSeconds to Hours --- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 9aa024fc8..fe11b619e 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -52,7 +52,7 @@ namespace Flow.Launcher.Plugin.PluginsManager if (string.IsNullOrWhiteSpace(search)) return Settings.HotKeys; - if ((DateTime.Now - _lastUpdateTime).TotalSeconds > 43200) // 12 hours + if ((DateTime.Now - _lastUpdateTime).TotalHours > 12) // 12 hours { Task.Run(async () => { From bb9682f7918ec6afa8ef0441dbe09fa8306a3b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 12:14:59 +0800 Subject: [PATCH 105/442] Move get default hotkeys to pluginmanger and make Main.Context non-static --- .../Main.cs | 2 +- .../PluginsManager.cs | 37 +++++++++++++++++ .../Settings.cs | 40 ------------------- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index fe11b619e..8d99bf37d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -13,7 +13,7 @@ namespace Flow.Launcher.Plugin.PluginsManager { public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n { - internal static PluginInitContext Context { get; set; } + internal PluginInitContext Context { get; set; } internal Settings Settings; diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index c6f127387..29ff9cb54 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -50,6 +50,43 @@ namespace Flow.Launcher.Plugin.PluginsManager await pluginsManifest.DownloadManifest(); } + internal List GetDefaultHotKeys() + { + return new List() + { + new Result() + { + Title = Settings.HotKeyInstall, + IcoPath = icoPath, + Action = _ => + { + Context.API.ChangeQuery("pm install "); + return false; + } + }, + new Result() + { + Title = Settings.HotkeyUninstall, + IcoPath = icoPath, + Action = _ => + { + Context.API.ChangeQuery("pm uninstall "); + return false; + } + }, + new Result() + { + Title = Settings.HotkeyUpdate, + IcoPath = icoPath, + Action = _ => + { + Context.API.ChangeQuery("pm update "); + return false; + } + } + }; + } + internal void InstallOrUpdate(UserPlugin plugin) { if (PluginExists(plugin.ID)) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index 5fc3916c0..3753056f7 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -12,46 +12,6 @@ namespace Flow.Launcher.Plugin.PluginsManager internal string HotkeyUpdate { get; set; } = "update"; internal readonly string icoPath = "Images\\pluginsmanager.png"; - - - internal List HotKeys - { - get - { - return new List() - { - new Result() - { - Title = HotKeyInstall, - IcoPath = icoPath, - Action = _ => - { - Main.Context.API.ChangeQuery("pm install "); - return false; - } - }, - new Result() - { - Title = HotkeyUninstall, - IcoPath = icoPath, - Action = _ => - { - Main.Context.API.ChangeQuery("pm uninstall "); - return false; - } - }, - new Result() - { - Title = HotkeyUpdate, - IcoPath = icoPath, - Action = _ => - { - Main.Context.API.ChangeQuery("pm update "); - return false; - } - } - }; - } } } } \ No newline at end of file From f32e202746397e6542f5a831b8b675ce5e1878c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 12:20:13 +0800 Subject: [PATCH 106/442] Use System.Text.JsonSerializer.DeserializeAsync instead of Newtonsoft.Json since our plugins.json can be large, so no need to create an extra string to store it. --- Flow.Launcher.Infrastructure/Http/Http.cs | 13 +++++++++++++ .../Models/PluginsManifest.cs | 8 ++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 2d798ec11..62cacc18f 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -73,5 +73,18 @@ namespace Flow.Launcher.Infrastructure.Http return content; } + + public static async Task GetStreamAsync([NotNull] string url, string encoding = "UTF-8") + { + Log.Debug($"|Http.Get|Url <{url}>"); + var request = WebRequest.CreateHttp(url); + request.Method = "GET"; + request.Timeout = 6000; + request.Proxy = WebProxy(); + request.UserAgent = UserAgent; + var response = await request.GetResponseAsync() as HttpWebResponse; + response = response.NonNull(); + return response.GetResponseStream().NonNull(); + } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs index 1c31432b2..2ab8806bc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -1,8 +1,8 @@ using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; -using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; namespace Flow.Launcher.Plugin.PluginsManager.Models @@ -17,12 +17,12 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models internal async Task DownloadManifest() { - var json = string.Empty; try { - json = await Http.Get("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json"); + var jsonStream = await Http.GetStreamAsync("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json") + .ConfigureAwait(false); - UserPlugins = JsonConvert.DeserializeObject>(json); + UserPlugins = await JsonSerializer.DeserializeAsync>(jsonStream).ConfigureAwait(false); } catch (Exception e) { From f0267475f520f9bd53981201dabc72e22877e2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 12:23:15 +0800 Subject: [PATCH 107/442] Change call method for default hotkeys. Remove extra whitespace add await using for stream --- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 4 ++-- .../Models/PluginsManifest.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 8d99bf37d..607b2e3f5 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin.PluginsManager var search = query.Search.ToLower(); if (string.IsNullOrWhiteSpace(search)) - return Settings.HotKeys; + return pluginManager.GetDefaultHotKeys(); if ((DateTime.Now - _lastUpdateTime).TotalHours > 12) // 12 hours { @@ -66,7 +66,7 @@ namespace Flow.Launcher.Plugin.PluginsManager var s when s.StartsWith(Settings.HotKeyInstall) => pluginManager.RequestInstallOrUpdate(s), var s when s.StartsWith(Settings.HotkeyUninstall) => pluginManager.RequestUninstall(s), var s when s.StartsWith(Settings.HotkeyUpdate) => pluginManager.RequestUpdate(s), - _ => Settings.HotKeys.Where(hotkey => + _ => pluginManager.GetDefaultHotKeys().Where(hotkey => { hotkey.Score = StringMatcher.FuzzySearch(search, hotkey.Title).Score; return hotkey.Score > 0; diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs index 2ab8806bc..c854b213d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -19,7 +19,7 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models { try { - var jsonStream = await Http.GetStreamAsync("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json") + await using var jsonStream = await Http.GetStreamAsync("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json") .ConfigureAwait(false); UserPlugins = await JsonSerializer.DeserializeAsync>(jsonStream).ConfigureAwait(false); @@ -33,4 +33,4 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models } } -} +} \ No newline at end of file From 5da8259228120ccbfed0662de1131bd3a95fed85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 13:03:38 +0800 Subject: [PATCH 108/442] fix an unintended text error add async await to download manifest task remove unused encoding name for new GetStreamAsync in Http fix unintened text error --- Flow.Launcher.Infrastructure/Http/Http.cs | 2 +- .../Models/PluginsManifest.cs | 2 +- Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 62cacc18f..ea412759e 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -74,7 +74,7 @@ namespace Flow.Launcher.Infrastructure.Http return content; } - public static async Task GetStreamAsync([NotNull] string url, string encoding = "UTF-8") + public static async Task GetStreamAsync([NotNull] string url) { Log.Debug($"|Http.Get|Url <{url}>"); var request = WebRequest.CreateHttp(url); diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs index c854b213d..7b2f8e372 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -12,7 +12,7 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models internal List UserPlugins { get; private set; } internal PluginsManifest() { - Task.Run(() => DownloadManifest()).Wait(); + Task.Run(async () => await DownloadManifest()).Wait(); } internal async Task DownloadManifest() diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index 3753056f7..d5d78d28a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -12,6 +12,6 @@ namespace Flow.Launcher.Plugin.PluginsManager internal string HotkeyUpdate { get; set; } = "update"; internal readonly string icoPath = "Images\\pluginsmanager.png"; - } + } } \ No newline at end of file From c94a2f4c9599c49ad00b3e524b28ec044eb22868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 13:06:08 +0800 Subject: [PATCH 109/442] Use string.CompareTo instead of equal to check for update --- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 29ff9cb54..2931afe30 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -164,7 +164,7 @@ namespace Flow.Launcher.Plugin.PluginsManager from existingPlugin in Context.API.GetAllPlugins() join pluginFromManifest in pluginsManifest.UserPlugins on existingPlugin.Metadata.ID equals pluginFromManifest.ID - where existingPlugin.Metadata.Version != pluginFromManifest.Version + where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) > 0 select new { From a1a45a49890001150b8d4c78f8f8b3c763402565 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 17:49:11 +1100 Subject: [PATCH 110/442] add eof --- Flow.Launcher.Infrastructure/Http/Http.cs | 2 +- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 2 +- .../Models/PluginsManifest.cs | 2 +- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 3 +-- Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index ea412759e..9d2fe7860 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -87,4 +87,4 @@ namespace Flow.Launcher.Infrastructure.Http return response.GetResponseStream().NonNull(); } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 607b2e3f5..2d9a29a2f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -89,4 +89,4 @@ namespace Flow.Launcher.Plugin.PluginsManager return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs index 7b2f8e372..ad8185620 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -33,4 +33,4 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 2931afe30..d06bbf1f9 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -361,7 +361,6 @@ namespace Flow.Launcher.Plugin.PluginsManager using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); } - private List AutoCompleteReturnAllResults(string search, string hotkey, string title, string subtitle) { if (!string.IsNullOrEmpty(search) @@ -392,4 +391,4 @@ namespace Flow.Launcher.Plugin.PluginsManager return new List(); } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index d5d78d28a..a885bfd15 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -14,4 +14,4 @@ namespace Flow.Launcher.Plugin.PluginsManager internal readonly string icoPath = "Images\\pluginsmanager.png"; } -} \ No newline at end of file +} From f9349a64e96226b725500752755834ff1d493dea Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 17:49:37 +1100 Subject: [PATCH 111/442] fix condition of version compare --- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index d06bbf1f9..9635648d4 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -164,7 +164,7 @@ namespace Flow.Launcher.Plugin.PluginsManager from existingPlugin in Context.API.GetAllPlugins() join pluginFromManifest in pluginsManifest.UserPlugins on existingPlugin.Metadata.ID equals pluginFromManifest.ID - where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) > 0 + where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) < 0 // if current version precedes manifest version select new { From e5b67ea10a43527c99e62317f466e2b14c467995 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 18:04:23 +1100 Subject: [PATCH 112/442] remove obsolete icoPath from settings --- Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index a885bfd15..9c5b0d29f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -10,8 +10,5 @@ namespace Flow.Launcher.Plugin.PluginsManager internal string HotkeyUninstall { get; set; } = "uninstall"; internal string HotkeyUpdate { get; set; } = "update"; - - internal readonly string icoPath = "Images\\pluginsmanager.png"; - } } From 0a47636bc9e0427341d97aa471a67a94fef38b3c Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 18:13:52 +1100 Subject: [PATCH 113/442] remove obsolete settings property in context menu class --- Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs | 7 ++----- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs index 76cb0f86b..b9bf5ee85 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.PluginsManager.Models; using System; using System.Collections.Generic; @@ -10,12 +10,9 @@ namespace Flow.Launcher.Plugin.PluginsManager { private PluginInitContext Context { get; set; } - private Settings Settings { get; set; } - - public ContextMenu(PluginInitContext context, Settings settings) + public ContextMenu(PluginInitContext context) { Context = context; - Settings = settings; } public List LoadContextMenus(Result selectedResult) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 2d9a29a2f..a333b3862 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -1,5 +1,4 @@ using Flow.Launcher.Infrastructure.Storage; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.PluginsManager.ViewModels; using Flow.Launcher.Plugin.PluginsManager.Views; using System.Collections.Generic; @@ -35,7 +34,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context = context; viewModel = new SettingsViewModel(context); Settings = viewModel.Settings; - contextMenu = new ContextMenu(Context, Settings); + contextMenu = new ContextMenu(Context); pluginManager = new PluginsManager(Context, Settings); _lastUpdateTime = DateTime.Now; } From a1d1c95eebb83867c4581759076b6167c7c0f65b Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 18:14:22 +1100 Subject: [PATCH 114/442] change to tab in browser --- Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs index b9bf5ee85..7bc357be4 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.PluginsManager.Models; using System; using System.Collections.Generic; @@ -55,7 +55,7 @@ namespace Flow.Launcher.Plugin.PluginsManager ? pluginManifestInfo.UrlSourceCode.Replace("/tree/master", "/issues/new/choose") : pluginManifestInfo.UrlSourceCode; - SharedCommands.SearchWeb.NewBrowserWindow(link); + SharedCommands.SearchWeb.NewTabInBrowser(link); return true; } }, @@ -66,7 +66,7 @@ namespace Flow.Launcher.Plugin.PluginsManager IcoPath = selectedResult.IcoPath, Action = _ => { - SharedCommands.SearchWeb.NewBrowserWindow("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"); + SharedCommands.SearchWeb.NewTabInBrowser("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"); return true; } } From b106688513f4d463656bd61c43034bcf71288acd Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 18:14:33 +1100 Subject: [PATCH 115/442] version bump --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index e970e5a8e..d94af71a1 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.3.0", + "Version": "1.3.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From 000bafd116910a5e8fcb919640be35f9ccfa2495 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 18:56:51 +1100 Subject: [PATCH 116/442] use camelCase and make last update time variable private --- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index a333b3862..716a424ff 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -22,7 +22,7 @@ namespace Flow.Launcher.Plugin.PluginsManager internal PluginsManager pluginManager; - internal DateTime _lastUpdateTime; + private DateTime lastUpdateTime; public Control CreateSettingPanel() { @@ -36,7 +36,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Settings = viewModel.Settings; contextMenu = new ContextMenu(Context); pluginManager = new PluginsManager(Context, Settings); - _lastUpdateTime = DateTime.Now; + lastUpdateTime = DateTime.Now; } public List LoadContextMenus(Result selectedResult) @@ -51,12 +51,12 @@ namespace Flow.Launcher.Plugin.PluginsManager if (string.IsNullOrWhiteSpace(search)) return pluginManager.GetDefaultHotKeys(); - if ((DateTime.Now - _lastUpdateTime).TotalHours > 12) // 12 hours + if ((DateTime.Now - lastUpdateTime).TotalHours > 12) // 12 hours { Task.Run(async () => { await pluginManager.UpdateManifest(); - _lastUpdateTime = DateTime.Now; + lastUpdateTime = DateTime.Now; }); } From a47f8b318510db0fe7e5edfacd91c1894e33221b Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 19:06:17 +1100 Subject: [PATCH 117/442] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f3f7e1a7..02f488758 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Windows may complain about security due to code not being signed, this will be c - Open flow's search window: Alt+Space is the default hotkey. - Open context menu: Ctrl+O/Shift+Enter. - Cancel/Return to previous screen: Esc. -- Install/Uninstall/Update plugins: in the search window, type `pm`/`pm uninstall`/`pm update` + the plugin name. +- Install/Uninstall/Update plugins: in the search window, type `pm install`/`pm uninstall`/`pm update` + the plugin name. - Saved user settings are located: - If using roaming: `%APPDATA%\FlowLauncher` - If using portable, by default: `%localappdata%\FlowLauncher\app-\UserData` From cfa93a2cc60808b84a131852a82f3e26ce1a4339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 17:13:31 +0800 Subject: [PATCH 118/442] Add GetStreamAsync method --- Flow.Launcher.Infrastructure/Http/Http.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 74e335d32..11d922aa1 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -110,5 +110,12 @@ namespace Flow.Launcher.Infrastructure.Http $"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); } } + + public static async Task GetStreamAsync([NotNull] string url) + { + Log.Debug($"|Http.Get|Url <{url}>"); + var response = await client.GetAsync(url); + return await response.Content.ReadAsStreamAsync(); + } } } From a806f7d05adc9beb7419e555921192e094c10539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 17:14:13 +0800 Subject: [PATCH 119/442] Change exception type --- Flow.Launcher.Infrastructure/Http/Http.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 11d922aa1..a98ead687 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -89,7 +89,7 @@ namespace Flow.Launcher.Infrastructure.Http } else { - throw new WebException($"Error code <{response.StatusCode}> returned from <{url}>"); + throw new HttpRequestException($"Error code <{response.StatusCode}> returned from <{url}>"); } } From 4d5119f17d2f5b307dd7eb33384e63ee31b48c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 17:15:34 +0800 Subject: [PATCH 120/442] Add out of bound exception for pattern matching --- Flow.Launcher.Infrastructure/Http/Http.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index a98ead687..040939e72 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -61,7 +61,7 @@ namespace Flow.Launcher.Infrastructure.Http { (_proxy.Address, _proxy.Credentials) = property switch { - ProxyProperty.Enabled => (Proxy.Enabled) switch + ProxyProperty.Enabled => Proxy.Enabled switch { true => Proxy.UserName switch { @@ -75,7 +75,8 @@ namespace Flow.Launcher.Infrastructure.Http ProxyProperty.Server => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), _proxy.Credentials), ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), _proxy.Credentials), ProxyProperty.UserName => (_proxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), - ProxyProperty.Password => (_proxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)) + ProxyProperty.Password => (_proxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), + _ => throw new ArgumentOutOfRangeException() }; } From e364b84b8458aa8df058fa9cf4507b2699d3224a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 17:16:44 +0800 Subject: [PATCH 121/442] Use auto property --- Flow.Launcher.Infrastructure/Http/Http.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 040939e72..3d056b28b 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -47,19 +47,14 @@ namespace Flow.Launcher.Infrastructure.Http } } - private static readonly WebProxy _proxy = new WebProxy(); - - public static WebProxy WebProxy - { - get { return _proxy; } - } + public static WebProxy WebProxy { get; } = new WebProxy(); /// /// Update the Address of the Proxy to modify the client Proxy /// public static void UpdateProxy(ProxyProperty property) { - (_proxy.Address, _proxy.Credentials) = property switch + (WebProxy.Address, WebProxy.Credentials) = property switch { ProxyProperty.Enabled => Proxy.Enabled switch { @@ -72,10 +67,10 @@ namespace Flow.Launcher.Infrastructure.Http }, false => (null, null) }, - ProxyProperty.Server => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), _proxy.Credentials), - ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), _proxy.Credentials), - ProxyProperty.UserName => (_proxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), - ProxyProperty.Password => (_proxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), + ProxyProperty.Server => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials), + ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials), + ProxyProperty.UserName => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), + ProxyProperty.Password => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)), _ => throw new ArgumentOutOfRangeException() }; } From 0c97db04d4b89d345ddb7908c5a6d361c11c0ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 17:50:56 +0800 Subject: [PATCH 122/442] 1. Change Get method Name to GetAsync 2. Manually replace "#" with "%23" to solve the similar issue in Explorer plugin 3. Add GetAsync method with Uri as argument 4. Remove unused encoding argument 5. Change exception type for WebSearch Plguin 6. Update Comment --- Flow.Launcher.Core/Updater.cs | 2 +- Flow.Launcher.Infrastructure/Http/Http.cs | 25 +++++++++++++++---- .../SuggestionSources/Baidu.cs | 5 ++-- .../SuggestionSources/Google.cs | 6 ++--- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index d8bc2b6dc..46fb6d977 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -133,7 +133,7 @@ namespace Flow.Launcher.Core var uri = new Uri(repository); var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; - var json = await Http.Get(api); + var json = await Http.GetAsync(api); var releases = JsonConvert.DeserializeObject>(json); var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 3d056b28b..8e2832690 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -89,13 +89,23 @@ namespace Flow.Launcher.Infrastructure.Http } } - public static async Task Get([NotNull] string url, string encoding = "UTF-8") + /// + /// Asynchrously get the result as string from url. + /// When supposing the result is long and large, try using GetStreamAsync to avoid reading as string + /// + /// + /// + public static Task GetAsync([NotNull] string url) { Log.Debug($"|Http.Get|Url <{url}>"); - var response = await client.GetAsync(url); - await using var stream = await response.Content.ReadAsStreamAsync(); - using var reader = new StreamReader(stream, Encoding.GetEncoding(encoding)); - var content = await reader.ReadToEndAsync(); + return GetAsync(new Uri(url.Replace("#", "%23"))); + } + + public static async Task GetAsync([NotNull] Uri url) + { + Log.Debug($"|Http.Get|Url <{url}>"); + using var response = await client.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.OK) { return content; @@ -107,6 +117,11 @@ namespace Flow.Launcher.Infrastructure.Http } } + /// + /// Asynchrously get the result as stream from url. + /// + /// + /// public static async Task GetStreamAsync([NotNull] string url) { Log.Debug($"|Http.Get|Url <{url}>"); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index 57db223bc..6772acf82 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; +using System.Net.Http; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { @@ -22,9 +23,9 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources try { const string api = "http://suggestion.baidu.com/su?json=1&wd="; - result = await Http.Get(api + Uri.EscapeUriString(query), "GB2312"); + result = await Http.GetAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); } - catch (WebException e) + catch (HttpRequestException e) { Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e); return new List(); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 81878bd8b..5b9538091 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; +using System.Net.Http; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { @@ -18,13 +19,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources try { const string api = "https://www.google.com/complete/search?output=chrome&q="; - result = await Http.Get(api + Uri.EscapeUriString(query)); + result = await Http.GetAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); } - catch (WebException e) + catch (HttpRequestException e) { Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); return new List(); - ; } if (string.IsNullOrEmpty(result)) return new List(); JContainer json; From efa4908f37636f3fe6122d241a5821d14a4bf6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 17:54:39 +0800 Subject: [PATCH 123/442] Change usage of Http in Updater.cs and adding ConfigureAwait(false) for await in checking update --- Flow.Launcher.Core/Updater.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 46fb6d977..05397e906 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -29,7 +29,7 @@ namespace Flow.Launcher.Core GitHubRepository = gitHubRepository; } - public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) + public async Task UpdateApp(IPublicAPI api, bool silentUpdate = true) { UpdateManager updateManager; UpdateInfo newUpdateInfo; @@ -39,7 +39,7 @@ namespace Flow.Launcher.Core try { - updateManager = await GitHubUpdateManager(GitHubRepository); + updateManager = await GitHubUpdateManager(GitHubRepository).ConfigureAwait(false); } catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { @@ -50,7 +50,7 @@ namespace Flow.Launcher.Core try { // UpdateApp CheckForUpdate will return value only if the app is squirrel installed - newUpdateInfo = await updateManager.CheckForUpdate().NonNull(); + newUpdateInfo = await updateManager.CheckForUpdate().NonNull().ConfigureAwait(false); } catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { @@ -85,8 +85,8 @@ namespace Flow.Launcher.Core updateManager.Dispose(); return; } - - await updateManager.ApplyReleases(newUpdateInfo); + + await updateManager.ApplyReleases(newUpdateInfo).ConfigureAwait(false); if (DataLocation.PortableDataLocationInUse()) { @@ -98,11 +98,11 @@ namespace Flow.Launcher.Core } else { - await updateManager.CreateUninstallerRegistryEntry(); + await updateManager.CreateUninstallerRegistryEntry().ConfigureAwait(false); } var newVersionTips = NewVersinoTips(newReleaseVersion.ToString()); - + Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}"); // always dispose UpdateManager @@ -133,9 +133,9 @@ namespace Flow.Launcher.Core var uri = new Uri(repository); var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; - var json = await Http.GetAsync(api); + var jsonStream = await Http.GetStreamAsync(api).ConfigureAwait(false); - var releases = JsonConvert.DeserializeObject>(json); + var releases = await System.Text.Json.JsonSerializer.DeserializeAsync>(jsonStream).ConfigureAwait(false); var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/"); From d4f94c66acfba5dddf0110b857519feed03eb99c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 18:33:53 +0800 Subject: [PATCH 124/442] Make InstallOrUpdate to async --- Flow.Launcher.Core/Updater.cs | 8 ++++---- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 2 +- .../Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 7 +++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 05397e906..1e4b0453c 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using System.Windows; using JetBrains.Annotations; using Squirrel; -using Newtonsoft.Json; using Flow.Launcher.Core.Resource; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Infrastructure; @@ -17,6 +16,7 @@ using Flow.Launcher.Infrastructure.Logger; using System.IO; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using System.Text.Json.Serialization; namespace Flow.Launcher.Core { @@ -117,13 +117,13 @@ namespace Flow.Launcher.Core [UsedImplicitly] private class GithubRelease { - [JsonProperty("prerelease")] + [JsonPropertyName("prerelease")] public bool Prerelease { get; [UsedImplicitly] set; } - [JsonProperty("published_at")] + [JsonPropertyName("published_at")] public DateTime PublishedAt { get; [UsedImplicitly] set; } - [JsonProperty("html_url")] + [JsonPropertyName("html_url")] public string HtmlUrl { get; [UsedImplicitly] set; } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 716a424ff..d700b9dfd 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -22,7 +22,7 @@ namespace Flow.Launcher.Plugin.PluginsManager internal PluginsManager pluginManager; - private DateTime lastUpdateTime; + private DateTime lastUpdateTime = DateTime.MinValue; public Control CreateSettingPanel() { diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 9635648d4..428610f43 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -87,7 +87,7 @@ namespace Flow.Launcher.Plugin.PluginsManager }; } - internal void InstallOrUpdate(UserPlugin plugin) + internal async Task InstallOrUpdate(UserPlugin plugin) { if (PluginExists(plugin.ID)) { @@ -127,7 +127,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); - Http.Download(plugin.UrlDownload, filePath); + await Http.Download(plugin.UrlDownload, filePath).ConfigureAwait(false); Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_download_success")); @@ -264,8 +264,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Action = e => { Application.Current.MainWindow.Hide(); - InstallOrUpdate(x); - + _ = InstallOrUpdate(x); // No need to wait return ShouldHideWindow; }, ContextData = x From d0743f627668fa5bbd55a3daef37f82064fc3a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 18:48:00 +0800 Subject: [PATCH 125/442] Await Http.Download in Update method --- .../PluginsManager.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 428610f43..f12112382 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -140,11 +140,8 @@ namespace Flow.Launcher.Plugin.PluginsManager Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); } - Application.Current.Dispatcher.Invoke(() => - { - Install(plugin, filePath); - Context.API.RestartApp(); - }); + Install(plugin, filePath); + Context.API.RestartApp(); } internal List RequestUpdate(string search) @@ -211,10 +208,14 @@ namespace Flow.Launcher.Plugin.PluginsManager var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, $"{x.Name}-{x.NewVersion}.zip"); - Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath); - Install(x.PluginNewUserPlugin, downloadToFilePath); - Context.API.RestartApp(); + Task.Run(async delegate + { + await Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false); + Install(x.PluginNewUserPlugin, downloadToFilePath); + + Context.API.RestartApp(); + }); return true; } From c485578cff927a1714344c0eca1ab82870673fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 29 Dec 2020 18:48:55 +0800 Subject: [PATCH 126/442] Use CompareTo to check update for InstallOrUpdate method --- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index f12112382..ac15618ca 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -92,7 +92,7 @@ namespace Flow.Launcher.Plugin.PluginsManager if (PluginExists(plugin.ID)) { if (Context.API.GetAllPlugins() - .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version != plugin.Version)) + .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0)) { if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), Context.API.GetTranslation("plugin_pluginsmanager_update_title"), From e2b0386818dca21d0ce9aba69cbb5331d524e676 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 22:32:14 +1100 Subject: [PATCH 127/442] remove Color plugin from Flow as a default plugin --- Flow.Launcher.sln | 16 --- .../Flow.Launcher.Plugin.Color.csproj | 101 --------------- .../Images/color.png | Bin 1856 -> 0 bytes .../Languages/de.xaml | 8 -- .../Languages/en.xaml | 8 -- .../Languages/pl.xaml | 8 -- .../Languages/sk.xaml | 8 -- .../Languages/tr.xaml | 8 -- .../Languages/zh-cn.xaml | 8 -- .../Languages/zh-tw.xaml | 7 - Plugins/Flow.Launcher.Plugin.Color/Main.cs | 121 ------------------ .../Flow.Launcher.Plugin.Color/plugin.json | 12 -- 12 files changed, 305 deletions(-) delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Images/color.png delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Languages/de.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Languages/en.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Languages/pl.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Languages/sk.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Languages/tr.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Languages/zh-cn.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Languages/zh-tw.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/Main.cs delete mode 100644 Plugins/Flow.Launcher.Plugin.Color/plugin.json diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln index 4d8997177..21c3b47dc 100644 --- a/Flow.Launcher.sln +++ b/Flow.Launcher.sln @@ -20,7 +20,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} - {F35190AA-4758-4D9E-A193-E3BDF6AD3567} = {F35190AA-4758-4D9E-A193-E3BDF6AD3567} {9B130CC5-14FB-41FF-B310-0A95B6894C37} = {9B130CC5-14FB-41FF-B310-0A95B6894C37} {FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {FDED22C8-B637-42E8-824A-63B5B6E05A3A} {A3DCCBCA-ACC1-421D-B16E-210896234C26} = {A3DCCBCA-ACC1-421D-B16E-210896234C26} @@ -44,8 +43,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Sys", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Url", "Plugins\Flow.Launcher.Plugin.Url\Flow.Launcher.Plugin.Url.csproj", "{A3DCCBCA-ACC1-421D-B16E-210896234C26}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Color", "Plugins\Flow.Launcher.Plugin.Color\Flow.Launcher.Plugin.Color.csproj", "{F35190AA-4758-4D9E-A193-E3BDF6AD3567}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FFD651C7-0546-441F-BC8C-D4EE8FD01EA7}" ProjectSection(SolutionItems) = preProject .gitattributes = .gitattributes @@ -214,18 +211,6 @@ Global {A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x64.Build.0 = Release|Any CPU {A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x86.ActiveCfg = Release|Any CPU {A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x86.Build.0 = Release|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x64.ActiveCfg = Debug|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x64.Build.0 = Debug|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x86.ActiveCfg = Debug|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x86.Build.0 = Debug|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|Any CPU.Build.0 = Release|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x64.ActiveCfg = Release|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x64.Build.0 = Release|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x86.ActiveCfg = Release|Any CPU - {F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x86.Build.0 = Release|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -309,7 +294,6 @@ Global {FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {A3DCCBCA-ACC1-421D-B16E-210896234C26} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} - {F35190AA-4758-4D9E-A193-E3BDF6AD3567} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} diff --git a/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj b/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj deleted file mode 100644 index c7fe8271a..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj +++ /dev/null @@ -1,101 +0,0 @@ - - - - Library - netcoreapp3.1 - {F35190AA-4758-4D9E-A193-E3BDF6AD3567} - Properties - Flow.Launcher.Plugin.Color - Flow.Launcher.Plugin.Color - true - false - false - - - - true - full - false - ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Color\ - DEBUG;TRACE - prompt - 4 - false - - - - pdbonly - true - ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Color\ - TRACE - prompt - 4 - false - - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Color/Images/color.png b/Plugins/Flow.Launcher.Plugin.Color/Images/color.png deleted file mode 100644 index da28583b1c9f174367acef0205f0c47f0a839ecd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1856 zcmV-G2fz4e%OapHR~QiSv}$ z`|SPw_WIV^7ZZNx5B<*d;}3xUcLaZx2=LHTUNG+g(Z&oQa{$CW^yENaxq-_Hz{8y` zB+|nR!VA?35U15cp8n}_fs_|uXnO#l4JE}f2Wac@mfsuz4{qxQDF;jmV{I>O9q2LD zR?=um5#Y|w4k*g=V0Hs=foL4e{dD9=9}hm|1EJlND8>kFJ=QUN>K-pK?EpdaHx2MO zlh)_CKj1Uf4`8m0a%ZOtN@jMwx*aq7cvt)$ve|1)4b15oU;X3{L`HVwUbbm_JuLxg zu9%7dcXjoEXiq6|kv09Hn$25QNg+5rlbrcJJi4~Jxm14)KIqx>MYn81qySml49hy2 zUj05uQO0UEZ)q@u$chKJd33EjSWK!%W+yp-iP;x$0zf>55P9N7J5vBAqF@Ff07I`{ zi%Skb7otG?5&%X#o4)8M6@4=RJ`hQ(OFqCN2&)3&d10G;AtobG7Jyz5J#Qul3fl-f zIUin605=tWXAS^;o=vCqD_^!lc?9Nbzo{fH6q%Za-{!7E#hvx7zX+flNh}8Dt}dyK z3N0kwOI$(_HU;%v>x#A)vIsC(YM+c%OIag&;Dc^J+ri013qF2zlC)Hj6>{%ST?q`? zS0}5TZak9vEOBxEuli?hTNuV4p7nor6^U~o53Ns+qKeaRn9P|e}CKHL8G?1 z0DRzxD+U?{vfE)1g=9QZ3L+Z4;E%;FwSq9_dI$}Q9X0s?iL^TN+E>xA%G5klU+a&q zs4KEnCfW<)Ewwq(3@tYSiO)kIZW0i6?P!i)LCd`5Rz30g5c}H(i*~U^0Q0tC!(C-52vto*rY;921%%@k z_${?a+Z$^s&{$(RIgxD5h+o_2hSj<-yVnHfY;$eq3vI)UUHcy$?9KnY7{Flm^Iwuh zB6jt{$t3+RR(;>B+ws6!92ifX|F6^Oh%T>N$d5(+>DWfk^TJAnQP7FNQI^&Gj90Tc z@#9ZZmoH5xD$}Z#IX)72tiH+_zHL=qL#=D|v%Z(dap;Wg-7mkp8lIS%ZD0+V1}wlbr1=*EsPVa(@V`!kr&FQJ5xP*NQELADt5K+i8pAHE zTX}Kop64Fh5`*>hJMUIce;sxvep*e?GL=k&Ef}qGU!AR6x8fh3^9_}DmhbKW$N@}P zC?_6iRd-AL?Ofddoo!j-RHygm_bM6MmHz*rB2|qltFDD?7In-dL~vi*;QT~oAH5{@ z0{pg-jY^46soKm?EE3iT%@cP&=pS0KK0WSKXzouxS`hoX6~tp$Zf}59DVl9F5T=q? z2sbLDvY9_Nb0wq_no@R0aO|0vHXJ*9PW;dD4QraU$-^NC`v{0t4&BH}Ac|&xKIe3v z%tFSBIO8tVYngaqn{ShItwQ|S9lPAB!%=(m?72+uv5jk>X>$==?w`y+RFNOmcHM={ z03v8j$2&|fWH~0WCNOiSmY8c132Wqe(f*jGetdD=9ge9D4=(sTA0XGK7X9)h(&I7I zr5-o3X%3>ouAL!5|Gr0t3~w-`yHPR7hr368KOiTgfXHDZq){ zqrC+AfkqDi3WW|=sZh1z1C1#FpbtCSukS!(#y%o8MF0wgmi=Yp$~gdsPo17DgfIob z7{Sx+*LNm9@#3ce(%G378fpyd=9h_ z!V~~-5IfsTUT#TqU`a`AAYKo`6hJwWI8#PVMB$KaF5~%}ZH#JGE)uDGEr!R_?7Pz0 zH(ZGe2OsvHaU~tIJ;v3DM-D%6dkfiYrgoG9;+=FrocfIJoBCkUy3yq+&*g3oNZ3gO zyac$J$pv7Xi2EOV{h{6$2I8V#OE18(eT}@at)c_(ed2}2ogj9DX&Quau+l55{rM9t z%>mg0mizvND!5 diff --git a/Plugins/Flow.Launcher.Plugin.Color/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Color/Languages/de.xaml deleted file mode 100644 index 3244dee14..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Languages/de.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Farben - Stellt eine HEX-Farben Vorschau bereit. (Versuche #000 in Flow Launcher) - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Color/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Color/Languages/en.xaml deleted file mode 100644 index 85e2830db..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Languages/en.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Colors - Allows to preview colors using hex values.(Try #000 in Flow Launcher) - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Color/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Color/Languages/pl.xaml deleted file mode 100644 index 15525cfe9..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Languages/pl.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Kolory - Podgląd kolorów po wpisaniu ich kodu szesnastkowego. (Spróbuj wpisać #000 w oknie Flow Launchera) - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Color/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Color/Languages/sk.xaml deleted file mode 100644 index 4b208691a..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Languages/sk.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Farby - Zobrazuje náhľad farieb v HEX formáte. (Skúste #000 vo Flow Launcheri) - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Color/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Color/Languages/tr.xaml deleted file mode 100644 index f56e73526..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Languages/tr.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Renkler - Hex kodunu girdiğiniz renkleri görüntülemeye yarar.(#000 yazmayı deneyin) - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Color/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Color/Languages/zh-cn.xaml deleted file mode 100644 index 39ede4844..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Languages/zh-cn.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - 颜色 - 提供在Flow Launcher查询hex颜色。(尝试在Flow Launcher中输入#000) - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Color/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Color/Languages/zh-tw.xaml deleted file mode 100644 index 4e7062a22..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Languages/zh-tw.xaml +++ /dev/null @@ -1,7 +0,0 @@ - - - 顏色 - 提供在 Flow Launcher 查詢 hex 顏色。(試著在 Flow Launcher 中輸入 #000) - diff --git a/Plugins/Flow.Launcher.Plugin.Color/Main.cs b/Plugins/Flow.Launcher.Plugin.Color/Main.cs deleted file mode 100644 index a15483ebc..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/Main.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Linq; -using System.Windows; - -namespace Flow.Launcher.Plugin.Color -{ - public sealed class ColorsPlugin : IPlugin, IPluginI18n - { - private string DIR_PATH = Path.Combine(Path.GetTempPath(), @"Plugins\Colors\"); - private PluginInitContext context; - private const int IMG_SIZE = 32; - - private DirectoryInfo ColorsDirectory { get; set; } - - public ColorsPlugin() - { - if (!Directory.Exists(DIR_PATH)) - { - ColorsDirectory = Directory.CreateDirectory(DIR_PATH); - } - else - { - ColorsDirectory = new DirectoryInfo(DIR_PATH); - } - } - - public List Query(Query query) - { - var raw = query.Search; - if (!IsAvailable(raw)) return new List(0); - try - { - var cached = Find(raw); - if (cached.Length == 0) - { - var path = CreateImage(raw); - return new List - { - new Result - { - Title = raw, - IcoPath = path, - Action = _ => - { - Clipboard.SetText(raw); - return true; - } - } - }; - } - return cached.Select(x => new Result - { - Title = raw, - IcoPath = x.FullName, - Action = _ => - { - Clipboard.SetText(raw); - return true; - } - }).ToList(); - } - catch (Exception exception) - { - // todo: log - return new List(0); - } - } - - private bool IsAvailable(string query) - { - // todo: rgb, names - var length = query.Length - 1; // minus `#` sign - return query.StartsWith("#") && (length == 3 || length == 6); - } - - public FileInfo[] Find(string name) - { - var file = string.Format("{0}.png", name.Substring(1)); - return ColorsDirectory.GetFiles(file, SearchOption.TopDirectoryOnly); - } - - private string CreateImage(string name) - { - using (var bitmap = new Bitmap(IMG_SIZE, IMG_SIZE)) - using (var graphics = Graphics.FromImage(bitmap)) - { - var color = ColorTranslator.FromHtml(name); - graphics.Clear(color); - - var path = CreateFileName(name); - bitmap.Save(path, ImageFormat.Png); - return path; - } - } - - private string CreateFileName(string name) - { - return string.Format("{0}{1}.png", ColorsDirectory.FullName, name.Substring(1)); - } - - public void Init(PluginInitContext context) - { - this.context = context; - } - - - public string GetTranslatedPluginTitle() - { - return context.API.GetTranslation("flowlauncher_plugin_color_plugin_name"); - } - - public string GetTranslatedPluginDescription() - { - return context.API.GetTranslation("flowlauncher_plugin_color_plugin_description"); - } - } -} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Color/plugin.json b/Plugins/Flow.Launcher.Plugin.Color/plugin.json deleted file mode 100644 index 8c0c483ba..000000000 --- a/Plugins/Flow.Launcher.Plugin.Color/plugin.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ID": "9B36CE6181FC47FBB597AA2C29CD9B0A", - "ActionKeyword": "*", - "Name": "Colors", - "Description": "Provide hex color preview.(Try #000 in Flow Launcher)", - "Author": "qianlifeng", - "Version": "1.1.1", - "Language": "csharp", - "Website": "https://github.com/Flow-Launcher/Flow.Launcher", - "ExecuteFileName": "Flow.Launcher.Plugin.Color.dll", - "IcoPath": "Images\\color.png" -} From 85584f40ee51d23e185ee67e4579859017dd1011 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 29 Dec 2020 22:41:21 +1100 Subject: [PATCH 128/442] remove post build script target of JsonRPC folder copy --- Scripts/post_build.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Scripts/post_build.ps1 b/Scripts/post_build.ps1 index 18ce33c4f..59036842a 100644 --- a/Scripts/post_build.ps1 +++ b/Scripts/post_build.ps1 @@ -36,7 +36,6 @@ function Copy-Resources ($path, $config) { $output = "$path\Output" $target = "$output\$config" Copy-Item -Recurse -Force $project\Images\* $target\Images\ - Copy-Item -Recurse -Force $path\JsonRPC $target\JsonRPC # making version static as multiple versions can exist in the nuget folder and in the case a breaking change is introduced. Copy-Item -Force $env:USERPROFILE\.nuget\packages\squirrel.windows\1.5.2\tools\Squirrel.exe $output\Update.exe } From d28b14ff2d4cb9a8d58d926f9d2542fd22941758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 30 Dec 2020 13:40:42 +0800 Subject: [PATCH 129/442] Replace All use of Json.Net with System.Text.Json --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 6 ++-- Flow.Launcher.Core/Plugin/PluginConfig.cs | 4 +-- Flow.Launcher.Core/Updater.cs | 1 - Flow.Launcher.Infrastructure/Helper.cs | 28 +++++++++++++------ Flow.Launcher.Infrastructure/Logger/Log.cs | 2 +- .../Storage/JsonStorage.cs | 16 +++++------ .../UserSettings/Settings.cs | 16 ++++++----- Flow.Launcher.Plugin/PluginMetadata.cs | 3 +- Flow.Launcher/Storage/QueryHistory.cs | 1 - Flow.Launcher/Storage/TopMostRecord.cs | 3 +- Flow.Launcher/Storage/UserSelectedRecord.cs | 2 -- .../Search/FolderLinks/FolderLink.cs | 8 +++--- .../Flow.Launcher.Plugin.Explorer/Settings.cs | 8 +----- .../SearchSource.cs | 2 +- .../Settings.cs | 2 +- .../SuggestionSources/Baidu.cs | 20 +++++-------- .../SuggestionSources/Google.cs | 22 ++++++--------- 17 files changed, 67 insertions(+), 77 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 31bf04286..3d4522498 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; -using Newtonsoft.Json; using Flow.Launcher.Infrastructure.Exception; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin; @@ -65,7 +65,7 @@ namespace Flow.Launcher.Core.Plugin { List results = new List(); - JsonRPCQueryResponseModel queryResponseModel = JsonConvert.DeserializeObject(output); + JsonRPCQueryResponseModel queryResponseModel = JsonSerializer.Deserialize(output); if (queryResponseModel.Result == null) return null; foreach (JsonRPCResult result in queryResponseModel.Result) @@ -84,7 +84,7 @@ namespace Flow.Launcher.Core.Plugin else { string actionReponse = ExecuteCallback(result1.JsonRPCAction); - JsonRPCRequestModel jsonRpcRequestModel = JsonConvert.DeserializeObject(actionReponse); + JsonRPCRequestModel jsonRpcRequestModel = JsonSerializer.Deserialize(actionReponse); if (jsonRpcRequestModel != null && !String.IsNullOrEmpty(jsonRpcRequestModel.Method) && jsonRpcRequestModel.Method.StartsWith("Flow.Launcher.")) diff --git a/Flow.Launcher.Core/Plugin/PluginConfig.cs b/Flow.Launcher.Core/Plugin/PluginConfig.cs index b946fa44d..46f79c60c 100644 --- a/Flow.Launcher.Core/Plugin/PluginConfig.cs +++ b/Flow.Launcher.Core/Plugin/PluginConfig.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.IO; -using Newtonsoft.Json; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin; +using System.Text.Json; namespace Flow.Launcher.Core.Plugin { @@ -61,7 +61,7 @@ namespace Flow.Launcher.Core.Plugin PluginMetadata metadata; try { - metadata = JsonConvert.DeserializeObject(File.ReadAllText(configPath)); + metadata = JsonSerializer.Deserialize(File.ReadAllText(configPath)); metadata.PluginDirectory = pluginDirectory; // for plugins which doesn't has ActionKeywords key metadata.ActionKeywords = metadata.ActionKeywords ?? new List { metadata.ActionKeyword }; diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 1e4b0453c..e1aa42730 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -13,7 +13,6 @@ using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; -using System.IO; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using System.Text.Json.Serialization; diff --git a/Flow.Launcher.Infrastructure/Helper.cs b/Flow.Launcher.Infrastructure/Helper.cs index fa7e18533..331b3a823 100644 --- a/Flow.Launcher.Infrastructure/Helper.cs +++ b/Flow.Launcher.Infrastructure/Helper.cs @@ -1,12 +1,19 @@ -using System; +using Newtonsoft.Json.Converters; +using System; using System.IO; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Flow.Launcher.Infrastructure { public static class Helper { + static Helper() + { + jsonFormattedSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + } + /// /// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy /// @@ -65,13 +72,18 @@ namespace Flow.Launcher.Infrastructure } } + private static readonly JsonSerializerOptions jsonFormattedSerializerOptions = new JsonSerializerOptions + { + WriteIndented = true + }; + public static string Formatted(this T t) { - var formatted = JsonConvert.SerializeObject( - t, - Formatting.Indented, - new StringEnumConverter() - ); + var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions + { + WriteIndented = true + }); + return formatted; } } diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 289ec5d68..91eeb183d 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -128,7 +128,7 @@ namespace Flow.Launcher.Infrastructure.Logger public static void Exception(string message, System.Exception e) { #if DEBUG - throw e; + throw e; #else if (FormatValid(message)) { diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 784c11110..268dc20b8 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; using System.IO; -using Newtonsoft.Json; +using System.Text.Json; using Flow.Launcher.Infrastructure.Logger; namespace Flow.Launcher.Infrastructure.Storage @@ -11,7 +11,7 @@ namespace Flow.Launcher.Infrastructure.Storage /// public class JsonStrorage { - private readonly JsonSerializerSettings _serializerSettings; + private readonly JsonSerializerOptions _serializerSettings; private T _data; // need a new directory name public const string DirectoryName = "Settings"; @@ -24,10 +24,9 @@ namespace Flow.Launcher.Infrastructure.Storage { // use property initialization instead of DefaultValueAttribute // easier and flexible for default value of object - _serializerSettings = new JsonSerializerSettings + _serializerSettings = new JsonSerializerOptions { - ObjectCreationHandling = ObjectCreationHandling.Replace, - NullValueHandling = NullValueHandling.Ignore + IgnoreNullValues = false }; } @@ -56,7 +55,7 @@ namespace Flow.Launcher.Infrastructure.Storage { try { - _data = JsonConvert.DeserializeObject(searlized, _serializerSettings); + _data = JsonSerializer.Deserialize(searlized, _serializerSettings); } catch (JsonException e) { @@ -77,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Storage BackupOriginFile(); } - _data = JsonConvert.DeserializeObject("{}", _serializerSettings); + _data = JsonSerializer.Deserialize("{}", _serializerSettings); Save(); } @@ -94,7 +93,8 @@ namespace Flow.Launcher.Infrastructure.Storage public void Save() { - string serialized = JsonConvert.SerializeObject(_data, Formatting.Indented); + string serialized = JsonSerializer.Serialize(_data, new JsonSerializerOptions() { WriteIndented = true }); + File.WriteAllText(FilePath, serialized); } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 832b6fbfa..bcfe298a9 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -1,8 +1,7 @@ using System; using System.Collections.ObjectModel; using System.Drawing; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings @@ -16,7 +15,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings public bool ShowOpenResultHotkey { get; set; } = true; public string Language { - get => language; set { + get => language; set + { language = value; OnPropertyChanged(); } @@ -73,9 +73,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public int MaxResultsToShow { get; set; } = 5; public int ActivateTimes { get; set; } - // Order defaults to 0 or -1, so 1 will let this property appear last - [JsonProperty(Order = 1)] - public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); + public ObservableCollection CustomPluginHotkeys { get; set; } = new ObservableCollection(); public bool DontPromptUpdateMsg { get; set; } @@ -100,8 +98,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings public HttpProxy Proxy { get; set; } = new HttpProxy(); - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected; + + + // Order defaults to 0 or -1, so 1 will let this property appear last + public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); } public enum LastQueryMode diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index d81b442e2..4c40be53c 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; using System.IO; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin { - [JsonObject(MemberSerialization.OptOut)] public class PluginMetadata : BaseModel { private string _pluginDirectory; diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs index de3bcaa22..2b2103605 100644 --- a/Flow.Launcher/Storage/QueryHistory.cs +++ b/Flow.Launcher/Storage/QueryHistory.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage diff --git a/Flow.Launcher/Storage/TopMostRecord.cs b/Flow.Launcher/Storage/TopMostRecord.cs index c110bdf92..09e69f6d8 100644 --- a/Flow.Launcher/Storage/TopMostRecord.cs +++ b/Flow.Launcher/Storage/TopMostRecord.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; +using System.Text.Json; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage @@ -8,7 +8,6 @@ namespace Flow.Launcher.Storage // todo this class is not thread safe.... but used from multiple threads. public class TopMostRecord { - [JsonProperty] private Dictionary records = new Dictionary(); internal bool IsTopMost(Result result) diff --git a/Flow.Launcher/Storage/UserSelectedRecord.cs b/Flow.Launcher/Storage/UserSelectedRecord.cs index 1fda04e9b..c7ffe1a1d 100644 --- a/Flow.Launcher/Storage/UserSelectedRecord.cs +++ b/Flow.Launcher/Storage/UserSelectedRecord.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Newtonsoft.Json; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin; @@ -7,7 +6,6 @@ namespace Flow.Launcher.Storage { public class UserSelectedRecord { - [JsonProperty] private Dictionary records = new Dictionary(); public void Add(Result result) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs index 379b5848f..43ecdad97 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs @@ -1,15 +1,15 @@ -using Newtonsoft.Json; -using System; +using System; using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks { - [JsonObject(MemberSerialization.OptIn)] public class FolderLink { - [JsonProperty] public string Path { get; set; } + [JsonIgnore] public string Nickname { get diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 5b12870c8..e62ea93fc 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,28 +1,22 @@ using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; -using Newtonsoft.Json; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.Explorer { public class Settings { - [JsonProperty] public int MaxResult { get; set; } = 100; - [JsonProperty] public List QuickFolderAccessLinks { get; set; } = new List(); - [JsonProperty] public bool UseWindowsIndexForDirectorySearch { get; set; } = true; - [JsonProperty] public List IndexSearchExcludedSubdirectoryPaths { get; set; } = new List(); - [JsonProperty] public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign; - [JsonProperty] public string FileContentSearchActionKeyword { get; set; } = Constants.DefaultContentSearchActionKeyword; } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSource.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSource.cs index c7ccb4d51..98e9376fb 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSource.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSource.cs @@ -1,10 +1,10 @@ using System.IO; using System.Windows.Media; using JetBrains.Annotations; -using Newtonsoft.Json; using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure; using System.Reflection; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.WebSearch { diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs index 555ee4647..e8881aae9 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs @@ -1,6 +1,6 @@ using System; using System.Collections.ObjectModel; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using Flow.Launcher.Plugin.WebSearch.SuggestionSources; namespace Flow.Launcher.Plugin.WebSearch diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index 6772acf82..2e385510f 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -2,10 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; using System.Net.Http; @@ -35,25 +34,20 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources Match match = _reg.Match(result); if (match.Success) { - JContainer json; + JsonDocument json; try { - json = JsonConvert.DeserializeObject(match.Groups[1].Value) as JContainer; + json = JsonDocument.Parse(match.Groups[1].Value); } - catch (JsonSerializationException e) + catch(JsonException e) { Log.Exception("|Baidu.Suggestions|can't parse suggestions", e); return new List(); } - if (json != null) - { - var results = json["s"] as JArray; - if (results != null) - { - return results.OfType().Select(o => o.Value).OfType().ToList(); - } - } + var results = json?.RootElement.GetProperty("s"); + + return results?.EnumerateArray().Select(o => o.GetString()).ToList() ?? new List(); } return new List(); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 5b9538091..ff0906b87 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -3,11 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; using System.Net.Http; +using System.Text.Json; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { @@ -27,25 +26,20 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources return new List(); } if (string.IsNullOrEmpty(result)) return new List(); - JContainer json; + JsonDocument json; try { - json = JsonConvert.DeserializeObject(result) as JContainer; + json = JsonDocument.Parse(result); } - catch (JsonSerializationException e) + catch (JsonException e) { Log.Exception("|Google.Suggestions|can't parse suggestions", e); return new List(); } - if (json != null) - { - var results = json[1] as JContainer; - if (results != null) - { - return results.OfType().Select(o => o.Value).OfType().ToList(); - } - } - return new List(); + + var results = json?.RootElement.EnumerateArray().ElementAt(1); + + return results?.EnumerateArray().Select(o => o.GetString()).ToList() ?? new List(); } public override string ToString() From 557842e8d797c3ca0146ed77d0a45906f5c6f3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 30 Dec 2020 13:48:06 +0800 Subject: [PATCH 130/442] remove dependency --- Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj | 1 - Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 28d4c0e1f..8153de6c8 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -49,7 +49,6 @@ - diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 70013c274..b7b52ac35 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -62,7 +62,6 @@ - \ No newline at end of file From 7bbd8c6069ef038186482b50d731cec90396b987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 30 Dec 2020 19:05:02 +0800 Subject: [PATCH 131/442] remove using from Helper.cs --- Flow.Launcher.Infrastructure/Helper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Helper.cs b/Flow.Launcher.Infrastructure/Helper.cs index 331b3a823..faa4c93b5 100644 --- a/Flow.Launcher.Infrastructure/Helper.cs +++ b/Flow.Launcher.Infrastructure/Helper.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json.Converters; -using System; +using System; using System.IO; using System.Runtime.CompilerServices; using System.Text.Json; From a0c4cc35756c42d7351bee37c795e063fd933dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 30 Dec 2020 19:09:52 +0800 Subject: [PATCH 132/442] use ParseAsync in Google --- .../SuggestionSources/Google.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index ff0906b87..f23cb66ff 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -7,6 +7,7 @@ using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; using System.Net.Http; using System.Text.Json; +using System.IO; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { @@ -14,22 +15,22 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { public override async Task> Suggestions(string query) { - string result; + Stream resultStream; try { const string api = "https://www.google.com/complete/search?output=chrome&q="; - result = await Http.GetAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); + resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); } catch (HttpRequestException e) { Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); return new List(); } - if (string.IsNullOrEmpty(result)) return new List(); + if (resultStream.Length == 0) return new List(); JsonDocument json; try { - json = JsonDocument.Parse(result); + json = await JsonDocument.ParseAsync(resultStream); } catch (JsonException e) { From a139a040ecc3bca388b77c7c4ba39de35eccd638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 30 Dec 2020 20:24:01 +0800 Subject: [PATCH 133/442] add bing search suggestion --- .../Settings.cs | 3 +- .../SuggestionSources/Bing.cs | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs index 555ee4647..1a3d9e5e5 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs @@ -196,7 +196,8 @@ namespace Flow.Launcher.Plugin.WebSearch [JsonIgnore] public SuggestionSource[] Suggestions { get; set; } = { new Google(), - new Baidu() + new Baidu(), + new Bing() }; [JsonIgnore] diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs new file mode 100644 index 000000000..9c4746711 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -0,0 +1,62 @@ +using Flow.Launcher.Infrastructure.Http; +using Flow.Launcher.Infrastructure.Logger; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Text.Json; +using System.Linq; + +namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources +{ + class Bing : SuggestionSource + { + public override async Task> Suggestions(string query) + { + Stream resultStream; + + try + { + const string api = "https://api.bing.com/qsonhs.aspx?q="; + resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); + } + catch (HttpRequestException e) + { + Log.Exception("|Bing.Suggestions|Can't get suggestion from Bing", e); + return new List(); + } + + if (resultStream.Length == 0) return new List(); + + JsonElement json; + try + { + json = (await JsonDocument.ParseAsync(resultStream)).RootElement.GetProperty("AS"); + } + catch (JsonException e) + { + Log.Exception("|Bing.Suggestions|can't parse suggestions", e); + return new List(); + } + + if (json.GetProperty("FullResults").GetInt32() == 0) + return new List(); + + return json.GetProperty("Results") + .EnumerateArray() + .SelectMany(r => r.GetProperty("Suggests") + .EnumerateArray() + .Select(s => s.GetProperty("Txt").GetString())) + .ToList(); + + } + + public override string ToString() + { + return "Bing"; + } + } +} From 5c17cb6e9b823482784f7ef0dda0df35f65eb932 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 31 Dec 2020 15:44:57 +1100 Subject: [PATCH 134/442] version bump WebSearch --- Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json index 329f1c41d..99fd2210a 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json @@ -25,7 +25,7 @@ "Name": "Web Searches", "Description": "Provide the web search ability", "Author": "qianlifeng", - "Version": "1.1.2", + "Version": "1.2.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll", From 27a0b934c612b8d3a482722c53910b7487a973d7 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 1 Jan 2021 18:39:41 +1100 Subject: [PATCH 135/442] update maintenance badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02f488758..654877f3a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

-![Maintenance](https://img.shields.io/maintenance/yes/2020) +![Maintenance](https://img.shields.io/maintenance/yes/2021) [![Build status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true&retina=true)](https://ci.appveyor.com/project/JohnTheGr8/flow-launcher/branch/dev) [![Github All Releases](https://img.shields.io/github/downloads/Flow-Launcher/Flow.Launcher/total.svg)](https://github.com/Flow-Launcher/Flow.Launcher/releases) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/Flow-Launcher/Flow.Launcher)](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest) From a65e17dba00830c32c746d7dea3b737fc518fa7a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 1 Jan 2021 18:57:58 +1100 Subject: [PATCH 136/442] fix wrong error message --- Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml | 4 +++- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index 8d24c145c..eaea9783b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -1,4 +1,4 @@ - @@ -6,6 +6,8 @@ Downloading plugin Please wait... Successfully downloaded + Error downloading plugin + Error occured while trying to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. Plugin Install diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index ac15618ca..a378a9046 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -134,8 +134,8 @@ namespace Flow.Launcher.Plugin.PluginsManager } catch (Exception e) { - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - Context.API.GetTranslation("plugin_pluginsmanager_download_success")); + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_download_error_title"), + Context.API.GetTranslation("plugin_pluginsmanager_download_error_subtitle")); Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); } From 54f6858937fdf3445e8503e7da398bafd0efa4b5 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 1 Jan 2021 19:03:06 +1100 Subject: [PATCH 137/442] add plugin name to error msg popup --- Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml | 2 +- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index eaea9783b..d0370ddc3 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -7,7 +7,7 @@ Please wait... Successfully downloaded Error downloading plugin - Error occured while trying to download the plugin + Error occured while trying to download {0} {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. Plugin Install diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index a378a9046..9d9d50902 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -135,7 +135,7 @@ namespace Flow.Launcher.Plugin.PluginsManager catch (Exception e) { Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_download_error_title"), - Context.API.GetTranslation("plugin_pluginsmanager_download_error_subtitle")); + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_error_subtitle"), plugin.Name)); Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); } From cab1b93183d4a8170f3b589354e1159b8e92cd7c Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 1 Jan 2021 19:13:14 +1100 Subject: [PATCH 138/442] move install method into try catch --- .../Languages/en.xaml | 4 ++-- .../PluginsManager.cs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index d0370ddc3..3017f39c3 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -6,13 +6,13 @@ Downloading plugin Please wait... Successfully downloaded - Error downloading plugin - Error occured while trying to download {0} {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. Plugin Install Plugin Uninstall Install failed: unable to find the plugin.json metadata file from the new plugin + Error installing plugin + Error occured while trying to install {0} No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 9d9d50902..db0327111 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -131,16 +131,17 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_download_success")); + + Install(plugin, filePath); } catch (Exception e) { - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_download_error_title"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_error_subtitle"), plugin.Name)); + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), plugin.Name)); - Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); + Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "InstallOrUpdate"); } - Install(plugin, filePath); Context.API.RestartApp(); } From 878f7bf5dfb154a2bf663deaf9256ce73ba3de1d Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 1 Jan 2021 19:49:19 +1100 Subject: [PATCH 139/442] version bump for PluginsManager --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index d94af71a1..7e78d65d6 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.3.1", + "Version": "1.3.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From 81cb20bf91edb292eb35445861a25acec31a2445 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 1 Jan 2021 21:54:34 +1100 Subject: [PATCH 140/442] add return to continue --- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index db0327111..880157a77 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -140,6 +140,8 @@ namespace Flow.Launcher.Plugin.PluginsManager string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), plugin.Name)); Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "InstallOrUpdate"); + + return; } Context.API.RestartApp(); From 6d66aa3f4415f114f44bb6b962651f5d269783f6 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 00:06:55 +1100 Subject: [PATCH 141/442] fix script to delete with condition dll version is same --- Scripts/post_build.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Scripts/post_build.ps1 b/Scripts/post_build.ps1 index 59036842a..b20393b02 100644 --- a/Scripts/post_build.ps1 +++ b/Scripts/post_build.ps1 @@ -44,8 +44,9 @@ function Delete-Unused ($path, $config) { $target = "$path\Output\$config" $included = Get-ChildItem $target -Filter "*.dll" foreach ($i in $included){ - Remove-Item -Path $target\Plugins -Include $i -Recurse - Write-Host "Deleting duplicated $i" + $deleteList = Get-ChildItem $target\Plugins -Include $i -Recurse | Where {$_.VersionInfo.FileVersion -eq $i.VersionInfo.FileVersion} + $deleteList | foreach-object{ Write-Host Deleting duplicated $_.Name with version $_.VersionInfo.FileVersion at location $_.Directory.FullName } + $deleteList | Remove-Item } Remove-Item -Path $target -Include "*.xml" -Recurse } From 29a382085fde4f5ac5ea173b5ccf338258d0c029 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 00:09:02 +1100 Subject: [PATCH 142/442] method name update --- Scripts/post_build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/post_build.ps1 b/Scripts/post_build.ps1 index b20393b02..e0868f3bb 100644 --- a/Scripts/post_build.ps1 +++ b/Scripts/post_build.ps1 @@ -45,7 +45,7 @@ function Delete-Unused ($path, $config) { $included = Get-ChildItem $target -Filter "*.dll" foreach ($i in $included){ $deleteList = Get-ChildItem $target\Plugins -Include $i -Recurse | Where {$_.VersionInfo.FileVersion -eq $i.VersionInfo.FileVersion} - $deleteList | foreach-object{ Write-Host Deleting duplicated $_.Name with version $_.VersionInfo.FileVersion at location $_.Directory.FullName } + $deleteList | ForEach-Object{ Write-Host Deleting duplicated $_.Name with version $_.VersionInfo.FileVersion at location $_.Directory.FullName } $deleteList | Remove-Item } Remove-Item -Path $target -Include "*.xml" -Recurse From f1badd6ae20466ab720b516d544a4b5f58b64dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Fri, 1 Jan 2021 21:19:05 +0800 Subject: [PATCH 143/442] Add Exception Handling for Querying plugins results --- Flow.Launcher/ViewModel/MainViewModel.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 7a3aa9f2f..2a69635bb 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -21,6 +21,7 @@ using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; using System.Windows.Media; using Flow.Launcher.Infrastructure.Image; +using Flow.Launcher.Infrastructure.Logger; namespace Flow.Launcher.ViewModel { @@ -414,8 +415,15 @@ namespace Flow.Launcher.ViewModel { if (!plugin.Metadata.Disabled) { - var results = PluginManager.QueryForPlugin(plugin, query); - UpdateResultView(results, plugin.Metadata, query); + try + { + var results = PluginManager.QueryForPlugin(plugin, query); + UpdateResultView(results, plugin.Metadata, query); + } + catch(Exception e) + { + Log.Exception($"|MainViewModel|Exception when querying {plugin.Metadata.Name}", e); + } } }); } @@ -432,7 +440,10 @@ namespace Flow.Launcher.ViewModel { // update to hidden if this is still the current query ProgressBarVisibility = Visibility.Hidden; } - }, currentCancellationToken); + }, currentCancellationToken).ContinueWith(t => + { + Log.Exception("|MainViewModel|Error when querying plugin", t.Exception?.InnerException); + }, TaskContinuationOptions.OnlyOnFaulted); } } else From f3355c525dfbdaa46ece70cb5197826c1bf354e9 Mon Sep 17 00:00:00 2001 From: Ioannis G Date: Fri, 1 Jan 2021 23:15:37 +0200 Subject: [PATCH 144/442] fix: properly include all plugin translation files --- ...low.Launcher.Plugin.BrowserBookmark.csproj | 9 +--- .../Flow.Launcher.Plugin.Calculator.csproj | 44 +----------------- .../Flow.Launcher.Plugin.ControlPanel.csproj | 45 +------------------ .../Flow.Launcher.Plugin.Explorer.csproj | 32 +------------ ...low.Launcher.Plugin.PluginIndicator.csproj | 42 +---------------- .../Flow.Launcher.Plugin.ProcessKiller.csproj | 2 +- .../Flow.Launcher.Plugin.Program.csproj | 27 +---------- .../Flow.Launcher.Plugin.Shell.csproj | 39 ++-------------- .../Flow.Launcher.Plugin.Sys.csproj | 27 +---------- .../Flow.Launcher.Plugin.Url.csproj | 36 +-------------- .../Flow.Launcher.Plugin.WebSearch.csproj | 27 +---------- 11 files changed, 17 insertions(+), 313 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index 85b745a6b..f308aa49a 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -43,11 +43,6 @@ Always - - Designer - MSBuild:Compile - PreserveNewest - Always @@ -60,9 +55,9 @@ - + - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index 9e1fefdb3..983ac160e 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -49,49 +49,9 @@ PreserveNewest - + - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj index 699737634..dce54cb52 100644 --- a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj +++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj @@ -48,50 +48,7 @@ PreserveNewest - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index a1a08843a..a9f675be8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -62,37 +62,7 @@ PreserveNewest - - MSBuild:Compile - Designer - PreserveNewest - - - - MSBuild:Compile - Designer - PreserveNewest - - - - MSBuild:Compile - Designer - PreserveNewest - - - - MSBuild:Compile - Designer - PreserveNewest - - - - MSBuild:Compile - Designer - PreserveNewest - - - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj index e6bfa7aa3..6abfc8469 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -52,47 +52,7 @@ - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index cf9c96294..a393d8510 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -39,7 +39,7 @@ PreserveNewest - + Designer MSBuild:Compile PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index 3802297c7..7e1b36fc1 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -68,35 +68,10 @@ Always - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index 178d95010..316742bf6 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -33,32 +33,6 @@ 4 false - - - - Always - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - @@ -72,18 +46,13 @@ + + Always + PreserveNewest - - - - - MSBuild:Compile - Designer - PreserveNewest - - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index bdab40457..61635dd08 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -52,32 +52,7 @@ PreserveNewest - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index 7d802d815..fb40c1e17 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -50,41 +50,9 @@ PreserveNewest - + - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - - MSBuild:Compile - Designer - PreserveNewest - - - - - + MSBuild:Compile Designer PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj index 431ca9ce8..e3bb4b408 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj @@ -89,32 +89,7 @@ PreserveNewest - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - - MSBuild:Compile - Designer - PreserveNewest - - + MSBuild:Compile Designer PreserveNewest From 8ab528c380acd0515b6576e74f76300763bd3bd6 Mon Sep 17 00:00:00 2001 From: Ioannis G Date: Sat, 2 Jan 2021 00:00:12 +0200 Subject: [PATCH 145/442] fix slovak translation --- Flow.Launcher/Languages/sk.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml index bf001d507..85fc1462c 100644 --- a/Flow.Launcher/Languages/sk.xaml +++ b/Flow.Launcher/Languages/sk.xaml @@ -45,8 +45,8 @@ Nová akcia skratky: Priečinok s pluginmi Autor - Príprava: {0}ms - Čas dopytu: {0}ms + Príprava: + Čas dopytu: Motív From 75f5a81a31ead865464f6a11f61f4c8fcf8d7cb6 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 11:55:06 +1100 Subject: [PATCH 146/442] update exception message --- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2a69635bb..eed30f377 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -422,7 +422,7 @@ namespace Flow.Launcher.ViewModel } catch(Exception e) { - Log.Exception($"|MainViewModel|Exception when querying {plugin.Metadata.Name}", e); + Log.Exception("MainViewModel", $"Exception when querying the plugin {plugin.Metadata.Name}", e, "QueryResults"); } } }); @@ -442,7 +442,7 @@ namespace Flow.Launcher.ViewModel } }, currentCancellationToken).ContinueWith(t => { - Log.Exception("|MainViewModel|Error when querying plugin", t.Exception?.InnerException); + Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults"); }, TaskContinuationOptions.OnlyOnFaulted); } } From 041bf4e9c4860b9114a95741fcb036d2f969d208 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 12:10:06 +1100 Subject: [PATCH 147/442] add condition to match name also --- Scripts/post_build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/post_build.ps1 b/Scripts/post_build.ps1 index e0868f3bb..836e27380 100644 --- a/Scripts/post_build.ps1 +++ b/Scripts/post_build.ps1 @@ -44,7 +44,7 @@ function Delete-Unused ($path, $config) { $target = "$path\Output\$config" $included = Get-ChildItem $target -Filter "*.dll" foreach ($i in $included){ - $deleteList = Get-ChildItem $target\Plugins -Include $i -Recurse | Where {$_.VersionInfo.FileVersion -eq $i.VersionInfo.FileVersion} + $deleteList = Get-ChildItem $target\Plugins -Include $i -Recurse | Where { $_.VersionInfo.FileVersion -eq $i.VersionInfo.FileVersion -And $_.Name -eq "$i" } $deleteList | ForEach-Object{ Write-Host Deleting duplicated $_.Name with version $_.VersionInfo.FileVersion at location $_.Directory.FullName } $deleteList | Remove-Item } From be1776fd2153f6bc9fbefbce71b313ea9ab9789e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 12:47:20 +1100 Subject: [PATCH 148/442] version bump for plugins --- Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Calculator/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.ControlPanel/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Explorer/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json | 2 +- .../Flow.Launcher.Plugin.PluginsManager.csproj | 2 +- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Program/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Shell/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Sys/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Url/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json index de4f3849b..b0c3d2e29 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser Bookmarks", "Description": "Search your browser bookmarks", "Author": "qianlifeng, Ioannis G.", - "Version": "1.3.1", + "Version": "1.3.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json index 709757d1a..7d9ca58d5 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json @@ -4,7 +4,7 @@ "Name": "Calculator", "Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)", "Author": "cxfksword", - "Version": "1.1.3", + "Version": "1.1.4", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Caculator.dll", diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/plugin.json b/Plugins/Flow.Launcher.Plugin.ControlPanel/plugin.json index 4f552a014..23f35e9ac 100644 --- a/Plugins/Flow.Launcher.Plugin.ControlPanel/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/plugin.json @@ -4,7 +4,7 @@ "Name": "Control Panel", "Description": "Search within the Control Panel.", "Author": "CoenraadS", - "Version": "1.1.1", + "Version": "1.1.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.ControlPanel.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index 2c57ac668..aa44c4413 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -7,7 +7,7 @@ "Name": "Explorer", "Description": "Search and manage files and folders. Explorer utilises Windows Index Search", "Author": "Jeremy Wu", - "Version": "1.2.5", + "Version": "1.2.6", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json index 80900a445..7f73263a8 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json @@ -4,7 +4,7 @@ "Name": "Plugin Indicator", "Description": "Provide plugin actionword suggestion", "Author": "qianlifeng", - "Version": "1.1.1", + "Version": "1.1.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index cc1a931ce..7beb8308b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -35,7 +35,7 @@ - + PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index d94af71a1..7e78d65d6 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.3.1", + "Version": "1.3.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json b/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json index d769397a8..2bb40c644 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json @@ -4,7 +4,7 @@ "Name":"Process Killer", "Description":"kill running processes from Flow", "Author":"Flow-Launcher", - "Version":"1.2.1", + "Version":"1.2.2", "Language":"csharp", "Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller", "IcoPath":"Images\\app.png", diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index 7d7a42e03..6c2c18e47 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "1.2.2", + "Version": "1.2.3", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json index 63e74d678..4ad572cf6 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json @@ -4,7 +4,7 @@ "Name": "Shell", "Description": "Provide executing commands from Flow Launcher. Commands should start with >", "Author": "qianlifeng", - "Version": "1.1.1", + "Version": "1.1.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json index 8d4b9a238..cf8ed6041 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json @@ -4,7 +4,7 @@ "Name": "System Commands", "Description": "Provide System related commands. e.g. shutdown,lock,setting etc.", "Author": "qianlifeng", - "Version": "1.1.1", + "Version": "1.1.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Url/plugin.json b/Plugins/Flow.Launcher.Plugin.Url/plugin.json index be64f6708..89dc20a33 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Url/plugin.json @@ -4,7 +4,7 @@ "Name": "URL", "Description": "Open the typed URL from Flow Launcher", "Author": "qianlifeng", - "Version": "1.1.1", + "Version": "1.1.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Url.dll", diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json index 99fd2210a..c036fbf8b 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json @@ -25,7 +25,7 @@ "Name": "Web Searches", "Description": "Provide the web search ability", "Author": "qianlifeng", - "Version": "1.2.0", + "Version": "1.2.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll", From 08f97f0ef9236e4572d7a707741488cddb568d38 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 12:57:13 +1100 Subject: [PATCH 149/442] change csproj images folder to use wildcard --- ...low.Launcher.Plugin.BrowserBookmark.csproj | 6 +- .../Flow.Launcher.Plugin.Calculator.csproj | 9 +-- .../Flow.Launcher.Plugin.ControlPanel.csproj | 4 +- .../Flow.Launcher.Plugin.Explorer.csproj | 35 +---------- ...low.Launcher.Plugin.PluginIndicator.csproj | 9 +-- ...Flow.Launcher.Plugin.PluginsManager.csproj | 9 +-- .../Flow.Launcher.Plugin.ProcessKiller.csproj | 6 +- .../Flow.Launcher.Plugin.Program.csproj | 21 +------ .../Flow.Launcher.Plugin.Shell.csproj | 9 +-- .../Flow.Launcher.Plugin.Sys.csproj | 50 +-------------- .../Flow.Launcher.Plugin.Url.csproj | 9 +-- .../Flow.Launcher.Plugin.WebSearch.csproj | 63 +------------------ 12 files changed, 33 insertions(+), 197 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index f308aa49a..d2a8736a6 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -40,9 +40,6 @@ - - Always - Always @@ -62,6 +59,9 @@ Designer PreserveNewest + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index 983ac160e..06d8dea7d 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -43,12 +43,6 @@ - - - - PreserveNewest - - @@ -56,6 +50,9 @@ Designer PreserveNewest + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj index dce54cb52..06969a135 100644 --- a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj +++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj @@ -45,9 +45,9 @@ - + PreserveNewest - + MSBuild:Compile Designer diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index a9f675be8..9d9c09a93 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -26,42 +26,9 @@ - - PreserveNewest - - - - PreserveNewest - - - - PreserveNewest - - - - PreserveNewest - - - - Always - - - + PreserveNewest - - - PreserveNewest - - - - PreserveNewest - - - - PreserveNewest - - MSBuild:Compile Designer diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj index 6abfc8469..0140444e1 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -45,18 +45,15 @@ - - - PreserveNewest - - - MSBuild:Compile Designer PreserveNewest + + PreserveNewest + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index 7beb8308b..2e352d832 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -27,17 +27,14 @@ PreserveNewest - - - - PreserveNewest - - PreserveNewest + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index a393d8510..a643ebf86 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -36,14 +36,14 @@ - - PreserveNewest - Designer MSBuild:Compile PreserveNewest + + PreserveNewest + Always diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index 7e1b36fc1..12e113855 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -52,30 +52,15 @@ - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - Always - - - MSBuild:Compile Designer PreserveNewest + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index 316742bf6..c542dc89b 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -46,17 +46,14 @@ - - Always - - - PreserveNewest - MSBuild:Compile Designer PreserveNewest + + PreserveNewest + MSBuild:Compile Designer diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index 61635dd08..8ef4dc0fb 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -40,23 +40,14 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - MSBuild:Compile Designer PreserveNewest + + PreserveNewest + MSBuild:Compile Designer @@ -68,39 +59,4 @@ PreserveNewest - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index fb40c1e17..671a8b1c2 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -44,12 +44,6 @@ - - - - PreserveNewest - - @@ -57,6 +51,9 @@ Designer PreserveNewest + + PreserveNewest + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj index e3bb4b408..af86af842 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj @@ -35,65 +35,14 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - MSBuild:Compile Designer PreserveNewest + + PreserveNewest + MSBuild:Compile Designer @@ -109,12 +58,6 @@ PreserveNewest - - - - PreserveNewest - - From 482988c288392af9b1bf2a911a6eb798c54f11ff Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 12:59:58 +1100 Subject: [PATCH 150/442] use wild card to include images for main project --- Flow.Launcher/Flow.Launcher.csproj | 108 +---------------------------- 1 file changed, 3 insertions(+), 105 deletions(-) diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 8548ba39e..39b2087aa 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -60,6 +60,9 @@ Designer PreserveNewest + + PreserveNewest + @@ -87,111 +90,6 @@ - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - From 97ac42751717121f5db1c695b81d5aed98df191f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 16:58:23 +1100 Subject: [PATCH 151/442] add manual reload of PluginsManifest data --- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index d700b9dfd..7c1eda3aa 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace Flow.Launcher.Plugin.PluginsManager { - public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n + public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n, IReloadable { internal PluginInitContext Context { get; set; } @@ -87,5 +87,14 @@ namespace Flow.Launcher.Plugin.PluginsManager { return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); } + + public void ReloadData() + { + Task.Run(async () => + { + await pluginManager.UpdateManifest(); + lastUpdateTime = DateTime.Now; + }); + } } } From 661c64b1c21b5e744ac4029b68e70d095a2074e2 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 17:00:10 +1100 Subject: [PATCH 152/442] version bump for PluginsManager --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 7e78d65d6..f5bbf3f6b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.3.2", + "Version": "1.4.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From b4b30dd34da9426a6f512966fca780c5d5ef1437 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 2 Jan 2021 18:06:44 +1100 Subject: [PATCH 153/442] wait manifest update before proceeding --- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 7c1eda3aa..f10f022d7 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -90,11 +90,8 @@ namespace Flow.Launcher.Plugin.PluginsManager public void ReloadData() { - Task.Run(async () => - { - await pluginManager.UpdateManifest(); - lastUpdateTime = DateTime.Now; - }); + Task.Run(() => pluginManager.UpdateManifest()).Wait(); + lastUpdateTime = DateTime.Now; } } } From 3cd609377e7402669c8dbbf67140bb58551d8ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 2 Jan 2021 15:52:41 +0800 Subject: [PATCH 154/442] Plugin Async ModelAdd Full Async model, including AsyncPlugin and AsyncReloadable --- Flow.Launcher.Core/Plugin/PluginManager.cs | 150 ++++++++++++------ Flow.Launcher.Infrastructure/Stopwatch.cs | 32 +++- Flow.Launcher.Plugin/IAsyncPlugin.cs | 12 ++ Flow.Launcher.Plugin/IPlugin.cs | 1 + Flow.Launcher.Plugin/IPublicAPI.cs | 3 +- .../Interfaces/IAsyncReloadable.cs | 9 ++ Flow.Launcher.Plugin/PluginPair.cs | 2 +- Flow.Launcher/PublicAPIInstance.cs | 4 +- Flow.Launcher/ViewModel/MainViewModel.cs | 38 ++--- .../ControlPanelList.cs | 2 +- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 41 +++-- 11 files changed, 210 insertions(+), 84 deletions(-) create mode 100644 Flow.Launcher.Plugin/IAsyncPlugin.cs create mode 100644 Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 3b697a1ee..239f0499d 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; @@ -32,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin /// /// Directories that will hold Flow Launcher plugin directory /// - private static readonly string[] Directories = { Constant.PreinstalledDirectory, DataLocation.PluginsDirectory }; + private static readonly string[] Directories = {Constant.PreinstalledDirectory, DataLocation.PluginsDirectory}; private static void DeletePythonBinding() { @@ -52,12 +53,19 @@ namespace Flow.Launcher.Core.Plugin } } - public static void ReloadData() + public static async Task ReloadData() { foreach(var plugin in AllPlugins) { - var reloadablePlugin = plugin.Plugin as IReloadable; - reloadablePlugin?.ReloadData(); + switch (plugin.Plugin) + { + case IReloadable p: + p.ReloadData(); + break; + case IAsyncReloadable p: + await p.ReloadDataAsync(); + break; + } } } @@ -86,24 +94,50 @@ namespace Flow.Launcher.Core.Plugin /// Call initialize for all plugins /// /// return the list of failed to init plugins or null for none - public static void InitializePlugins(IPublicAPI api) + public static async Task InitializePlugins(IPublicAPI api) { API = api; var failedPlugins = new ConcurrentQueue(); - Parallel.ForEach(AllPlugins, pair => + + var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate { try { - var milliseconds = Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", () => + long milliseconds; + + switch (pair.Plugin) { - pair.Plugin.Init(new PluginInitContext - { - CurrentPluginMetadata = pair.Metadata, - API = API - }); - }); + case IAsyncPlugin plugin: + milliseconds = await Stopwatch.DebugAsync( + $"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", + async delegate + { + await plugin.InitAsync(new PluginInitContext + { + CurrentPluginMetadata = pair.Metadata, + API = API + }); + }); + break; + case IPlugin plugin: + milliseconds = Stopwatch.Debug( + $"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", + () => + { + plugin.Init(new PluginInitContext + { + CurrentPluginMetadata = pair.Metadata, + API = API + }); + }); + break; + default: + throw new ArgumentException(); + } + pair.Metadata.InitTime += milliseconds; - Log.Info($"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); + Log.Info( + $"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); } catch (Exception e) { @@ -111,25 +145,33 @@ namespace Flow.Launcher.Core.Plugin pair.Metadata.Disabled = true; failedPlugins.Enqueue(pair); } - }); + })); + + await Task.WhenAll(InitTasks); _contextMenuPlugins = GetPluginsForInterface(); foreach (var plugin in AllPlugins) { - if (IsGlobalPlugin(plugin.Metadata)) - GlobalPlugins.Add(plugin); - - // Plugins may have multiple ActionKeywords, eg. WebSearch - plugin.Metadata.ActionKeywords - .Where(x => x != Query.GlobalPluginWildcardSign) - .ToList() - .ForEach(x => NonGlobalPlugins[x] = plugin); + foreach (var actionKeyword in plugin.Metadata.ActionKeywords) + { + switch (actionKeyword) + { + case Query.GlobalPluginWildcardSign: + GlobalPlugins.Add(plugin); + break; + default: + NonGlobalPlugins[actionKeyword] = plugin; + break; + } + } } if (failedPlugins.Any()) { var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); - API.ShowMsg($"Fail to Init Plugins", $"Plugins: {failed} - fail to load and would be disabled, please contact plugin creator for help", "", false); + API.ShowMsg($"Fail to Init Plugins", + $"Plugins: {failed} - fail to load and would be disabled, please contact plugin creator for help", + "", false); } } @@ -138,7 +180,7 @@ namespace Flow.Launcher.Core.Plugin if (NonGlobalPlugins.ContainsKey(query.ActionKeyword)) { var plugin = NonGlobalPlugins[query.ActionKeyword]; - return new List { plugin }; + return new List {plugin}; } else { @@ -146,25 +188,42 @@ namespace Flow.Launcher.Core.Plugin } } - public static List QueryForPlugin(PluginPair pair, Query query) + public static async Task> QueryForPlugin(PluginPair pair, Query query, CancellationToken token) { var results = new List(); try { var metadata = pair.Metadata; - var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () => - { - results = pair.Plugin.Query(query) ?? new List(); - UpdatePluginMetadata(results, metadata, query); - }); + var milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", + async () => + { + switch (pair.Plugin) + { + case IAsyncPlugin plugin: + results = await plugin.QueryAsync(query, token).ConfigureAwait(false) ?? + new List(); + UpdatePluginMetadata(results, metadata, query); + break; + case IPlugin plugin: + results = await Task.Run(() => plugin.Query(query), token).ConfigureAwait(false) ?? + new List(); + UpdatePluginMetadata(results, metadata, query); + break; + } + }); metadata.QueryCount += 1; - metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2; + metadata.AvgQueryTime = + metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2; } catch (Exception e) { - Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e); + Log.Exception( + $"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", + e); } - return results; + + // null will be fine since the results will only be added into queue if the token hasn't been cancelled + return token.IsCancellationRequested ? results = null : results; } public static void UpdatePluginMetadata(List results, PluginMetadata metadata, Query query) @@ -182,11 +241,6 @@ namespace Flow.Launcher.Core.Plugin } } - private static bool IsGlobalPlugin(PluginMetadata metadata) - { - return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign); - } - /// /// get specified plugin, return null if not found /// @@ -208,7 +262,7 @@ namespace Flow.Launcher.Core.Plugin var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID); if (pluginPair != null) { - var plugin = (IContextMenu)pluginPair.Plugin; + var plugin = (IContextMenu) pluginPair.Plugin; try { @@ -222,16 +276,19 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>", e); + Log.Exception( + $"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>", + e); } } + return results; } public static bool ActionKeywordRegistered(string actionKeyword) { return actionKeyword != Query.GlobalPluginWildcardSign - && NonGlobalPlugins.ContainsKey(actionKeyword); + && NonGlobalPlugins.ContainsKey(actionKeyword); } /// @@ -249,6 +306,7 @@ namespace Flow.Launcher.Core.Plugin { NonGlobalPlugins[newActionKeyword] = plugin; } + plugin.Metadata.ActionKeywords.Add(newActionKeyword); } @@ -262,9 +320,9 @@ namespace Flow.Launcher.Core.Plugin if (oldActionkeyword == Query.GlobalPluginWildcardSign && // Plugins may have multiple ActionKeywords that are global, eg. WebSearch plugin.Metadata.ActionKeywords - .Where(x => x == Query.GlobalPluginWildcardSign) - .ToList() - .Count == 1) + .Where(x => x == Query.GlobalPluginWildcardSign) + .ToList() + .Count == 1) { GlobalPlugins.Remove(plugin); } @@ -285,4 +343,4 @@ namespace Flow.Launcher.Core.Plugin } } } -} +} \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Stopwatch.cs b/Flow.Launcher.Infrastructure/Stopwatch.cs index d39d90e81..dd6edaff9 100644 --- a/Flow.Launcher.Infrastructure/Stopwatch.cs +++ b/Flow.Launcher.Infrastructure/Stopwatch.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; namespace Flow.Launcher.Infrastructure @@ -22,7 +23,22 @@ namespace Flow.Launcher.Infrastructure Log.Debug(info); return milliseconds; } - + + /// + /// This stopwatch will appear only in Debug mode + /// + public static async Task DebugAsync(string message, Func action) + { + var stopWatch = new System.Diagnostics.Stopwatch(); + stopWatch.Start(); + await action(); + stopWatch.Stop(); + var milliseconds = stopWatch.ElapsedMilliseconds; + string info = $"{message} <{milliseconds}ms>"; + Log.Debug(info); + return milliseconds; + } + public static long Normal(string message, Action action) { var stopWatch = new System.Diagnostics.Stopwatch(); @@ -34,6 +50,20 @@ namespace Flow.Launcher.Infrastructure Log.Info(info); return milliseconds; } + + public static async Task NormalAsync(string message, Func action) + { + var stopWatch = new System.Diagnostics.Stopwatch(); + stopWatch.Start(); + await action(); + stopWatch.Stop(); + var milliseconds = stopWatch.ElapsedMilliseconds; + string info = $"{message} <{milliseconds}ms>"; + Log.Info(info); + return milliseconds; + } + + public static void StartCount(string name, Action action) { diff --git a/Flow.Launcher.Plugin/IAsyncPlugin.cs b/Flow.Launcher.Plugin/IAsyncPlugin.cs new file mode 100644 index 000000000..36f098e7d --- /dev/null +++ b/Flow.Launcher.Plugin/IAsyncPlugin.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin +{ + public interface IAsyncPlugin + { + Task> QueryAsync(Query query, CancellationToken token); + Task InitAsync(PluginInitContext context); + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin/IPlugin.cs b/Flow.Launcher.Plugin/IPlugin.cs index 8f7d279fa..4cc6d8d40 100644 --- a/Flow.Launcher.Plugin/IPlugin.cs +++ b/Flow.Launcher.Plugin/IPlugin.cs @@ -5,6 +5,7 @@ namespace Flow.Launcher.Plugin public interface IPlugin { List Query(Query query); + void Init(PluginInitContext context); } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index ccc00d5e9..12e430e07 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin { @@ -34,7 +35,7 @@ namespace Flow.Launcher.Plugin /// Plugin's in memory data with new content /// added by user. /// - void ReloadAllPluginData(); + Task ReloadAllPluginData(); /// /// Check for new Flow Launcher update diff --git a/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs new file mode 100644 index 000000000..9c922f667 --- /dev/null +++ b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin +{ + public interface IAsyncReloadable + { + Task ReloadDataAsync(); + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin/PluginPair.cs b/Flow.Launcher.Plugin/PluginPair.cs index 910367ec6..e8954b7a0 100644 --- a/Flow.Launcher.Plugin/PluginPair.cs +++ b/Flow.Launcher.Plugin/PluginPair.cs @@ -2,7 +2,7 @@ { public class PluginPair { - public IPlugin Plugin { get; internal set; } + public object Plugin { get; internal set; } public PluginMetadata Metadata { get; internal set; } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 90d4fff63..bcf147be7 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -78,9 +78,9 @@ namespace Flow.Launcher ImageLoader.Save(); } - public void ReloadAllPluginData() + public async Task ReloadAllPluginData() { - PluginManager.ReloadData(); + await PluginManager.ReloadData(); } public void ShowMsg(string title, string subTitle = "", string iconPath = "") diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index eed30f377..bc68eb6d6 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -405,45 +405,47 @@ namespace Flow.Launcher.ViewModel }, currentCancellationToken); var plugins = PluginManager.ValidPluginsForQuery(query); - Task.Run(() => + Task.Run(async () => { // so looping will stop once it was cancelled + + Task[] tasks = new Task[plugins.Count]; var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; try { - Parallel.ForEach(plugins, parallelOptions, plugin => + Parallel.For(0, plugins.Count, parallelOptions, i => { - if (!plugin.Metadata.Disabled) + if (!plugins[i].Metadata.Disabled) { - try - { - var results = PluginManager.QueryForPlugin(plugin, query); - UpdateResultView(results, plugin.Metadata, query); - } - catch(Exception e) - { - Log.Exception("MainViewModel", $"Exception when querying the plugin {plugin.Metadata.Name}", e, "QueryResults"); - } + tasks[i] = QueryTask(i, query, currentCancellationToken); } + else tasks[i] = Task.CompletedTask; // Avoid Null }); + + // Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first + await Task.WhenAll(tasks); } 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 _isQueryRunning = false; - if (currentUpdateSource == _updateSource) + if (!currentCancellationToken.IsCancellationRequested) { // update to hidden if this is still the current query ProgressBarVisibility = Visibility.Hidden; } - }, currentCancellationToken).ContinueWith(t => - { - Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults"); - }, TaskContinuationOptions.OnlyOnFaulted); + + async Task QueryTask(int pairIndex, Query query, CancellationToken token) + { + var result = await PluginManager.QueryForPlugin(plugins[pairIndex], query, token); + UpdateResultView(result, plugins[pairIndex].Metadata, query); + } + + }, currentCancellationToken).ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), + TaskContinuationOptions.OnlyOnFaulted); } } else diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/ControlPanelList.cs b/Plugins/Flow.Launcher.Plugin.ControlPanel/ControlPanelList.cs index fdcffb0b3..70afda536 100644 --- a/Plugins/Flow.Launcher.Plugin.ControlPanel/ControlPanelList.cs +++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/ControlPanelList.cs @@ -38,7 +38,7 @@ namespace Flow.Launcher.Plugin.ControlPanel int cxDesired, int cyDesired, uint fuLoad); [DllImport("user32.dll", CharSet = CharSet.Auto)] - extern static bool DestroyIcon(IntPtr handle); + static extern bool DestroyIcon(IntPtr handle); [DllImport("kernel32.dll")] static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType); diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 5642b62ed..7ebab91a1 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading.Tasks; using System.Windows; using System.Windows.Forms; using System.Windows.Interop; @@ -67,13 +68,15 @@ namespace Flow.Launcher.Plugin.Sys { c.TitleHighlightData = titleMatch.MatchData; } - else + else { c.SubTitleHighlightData = subTitleMatch.MatchData; } + results.Add(c); } } + return results; } @@ -94,13 +97,15 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\shutdown.png", Action = c => { - var reuslt = MessageBox.Show(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"), - context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"), - MessageBoxButton.YesNo, MessageBoxImage.Warning); + var reuslt = MessageBox.Show( + context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"), + context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"), + MessageBoxButton.YesNo, MessageBoxImage.Warning); if (reuslt == MessageBoxResult.Yes) { Process.Start("shutdown", "/s /t 0"); } + return true; } }, @@ -111,13 +116,15 @@ namespace Flow.Launcher.Plugin.Sys IcoPath = "Images\\restart.png", Action = c => { - var result = MessageBox.Show(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"), - context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), - MessageBoxButton.YesNo, MessageBoxImage.Warning); + var result = MessageBox.Show( + context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"), + context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), + MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) { Process.Start("shutdown", "/r /t 0"); } + return true; } }, @@ -163,14 +170,16 @@ namespace Flow.Launcher.Plugin.Sys // http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html // FYI, couldn't find documentation for this but if the recycle bin is already empty, it will return -2147418113 (0x8000FFFF (E_UNEXPECTED)) // 0 for nothing - var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0); - if (result != (uint) HRESULT.S_OK && result != (uint)0x8000FFFF) + var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, + 0); + if (result != (uint) HRESULT.S_OK && result != (uint) 0x8000FFFF) { MessageBox.Show($"Error emptying recycle bin, error code: {result}\n" + "please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137", - "Error", - MessageBoxButton.OK, MessageBoxImage.Error); + "Error", + MessageBoxButton.OK, MessageBoxImage.Error); } + return true; } }, @@ -229,9 +238,13 @@ namespace Flow.Launcher.Plugin.Sys { // Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen. Application.Current.MainWindow.Hide(); - context.API.ReloadAllPluginData(); - context.API.ShowMsg(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), - context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")); + + context.API.ReloadAllPluginData().ContinueWith(_ => + context.API.ShowMsg( + context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), + context.API.GetTranslation( + "flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded"))); + return true; } }, From b8f7d899709ee4204d2a0f1cfae8722a2b80cdbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 2 Jan 2021 17:25:13 +0800 Subject: [PATCH 155/442] Allows Loading both IPlugin and IAsyncPlugin --- .../Plugin/PluginAssemblyLoader.cs | 10 +- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 100 +++++++++--------- Flow.Launcher/ViewModel/MainViewModel.cs | 9 +- 3 files changed, 61 insertions(+), 58 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs index b9b878a7b..1a1b17539 100644 --- a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs @@ -20,7 +20,7 @@ namespace Flow.Launcher.Core.Plugin dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath); assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath)); - referencedPluginPackageDependencyResolver = + referencedPluginPackageDependencyResolver = new AssemblyDependencyResolver(Path.Combine(Constant.ProgramDirectory, "Flow.Launcher.Plugin.dll")); } @@ -38,15 +38,15 @@ namespace Flow.Launcher.Core.Plugin // that use Newtonsoft.Json if (assemblyPath == null || ExistsInReferencedPluginPackage(assemblyName)) return null; - + return LoadFromAssemblyPath(assemblyPath); } - internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type) + internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, params Type[] types) { var allTypes = assembly.ExportedTypes; - return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(type)); + return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Intersect(types).Any()); } internal bool ExistsInReferencedPluginPackage(AssemblyName assemblyName) @@ -54,4 +54,4 @@ namespace Flow.Launcher.Core.Plugin return referencedPluginPackageDependencyResolver.ResolveAssemblyToPath(assemblyName) != null; } } -} +} \ No newline at end of file diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 224dbd85e..8295761b5 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -37,56 +37,59 @@ namespace Flow.Launcher.Core.Plugin foreach (var metadata in metadatas) { - var milliseconds = Stopwatch.Debug($"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () => - { - + var milliseconds = Stopwatch.Debug( + $"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () => + { #if DEBUG - var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath); - var assembly = assemblyLoader.LoadAssemblyAndDependencies(); - var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin)); - var plugin = (IPlugin)Activator.CreateInstance(type); -#else - Assembly assembly = null; - IPlugin plugin = null; - - try - { var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath); - assembly = assemblyLoader.LoadAssemblyAndDependencies(); + var assembly = assemblyLoader.LoadAssemblyAndDependencies(); + var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin), + typeof(IAsyncPlugin)); - var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin)); + var plugin = Activator.CreateInstance(type); +#else + Assembly assembly = null; + IPlugin plugin = null; - plugin = (IPlugin)Activator.CreateInstance(type); - } - catch (Exception e) when (assembly == null) - { - Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e); - } - catch (InvalidOperationException e) - { - Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); - } - catch (ReflectionTypeLoadException e) - { - Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); - } - catch (Exception e) - { - Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e); - } + try + { + var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath); + assembly = assemblyLoader.LoadAssemblyAndDependencies(); - if (plugin == null) - { - erroredPlugins.Add(metadata.Name); - return; - } + var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin), + typeof(IAsyncPlugin)); + + plugin = Activator.CreateInstance(type); + } + catch (Exception e) when (assembly == null) + { + Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e); + } + catch (InvalidOperationException e) + { + Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); + } + catch (ReflectionTypeLoadException e) + { + Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); + } + catch (Exception e) + { + Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e); + } + + if (plugin == null) + { + erroredPlugins.Add(metadata.Name); + return; + } #endif - plugins.Add(new PluginPair - { - Plugin = plugin, - Metadata = metadata + plugins.Add(new PluginPair + { + Plugin = plugin, + Metadata = metadata + }); }); - }); metadata.InitTime += milliseconds; } @@ -95,15 +98,15 @@ namespace Flow.Launcher.Core.Plugin var errorPluginString = String.Join(Environment.NewLine, erroredPlugins); var errorMessage = "The following " - + (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ") - + "errored and cannot be loaded:"; + + (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ") + + "errored and cannot be loaded:"; Task.Run(() => { MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + - $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + - $"Please refer to the logs for more information","", - MessageBoxButtons.OK, MessageBoxIcon.Warning); + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + + $"Please refer to the logs for more information", "", + MessageBoxButtons.OK, MessageBoxIcon.Warning); }); } @@ -179,6 +182,5 @@ namespace Flow.Launcher.Core.Plugin Metadata = metadata }); } - } } \ No newline at end of file diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index bc68eb6d6..3e1a4b516 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -417,7 +417,7 @@ namespace Flow.Launcher.ViewModel { if (!plugins[i].Metadata.Disabled) { - tasks[i] = QueryTask(i, query, currentCancellationToken); + tasks[i] = QueryTask(plugins[i], query, currentCancellationToken); } else tasks[i] = Task.CompletedTask; // Avoid Null }); @@ -438,10 +438,11 @@ namespace Flow.Launcher.ViewModel ProgressBarVisibility = Visibility.Hidden; } - async Task QueryTask(int pairIndex, Query query, CancellationToken token) + // Local Function + async Task QueryTask(PluginPair plugin, Query query, CancellationToken token) { - var result = await PluginManager.QueryForPlugin(plugins[pairIndex], query, token); - UpdateResultView(result, plugins[pairIndex].Metadata, query); + var results = await PluginManager.QueryForPlugin(plugin, query, token); + UpdateResultView(results, plugin.Metadata, query); } }, currentCancellationToken).ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), From f768b0890b8f784983da9cc2ae5a5f2b464a41ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 2 Jan 2021 17:58:30 +0800 Subject: [PATCH 156/442] Move Program Plugin to Async model --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 164 ++++++++++--------- 1 file changed, 88 insertions(+), 76 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 8f124f3a4..0d693d363 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; using Flow.Launcher.Infrastructure.Logger; @@ -12,9 +13,8 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program { - public class Main : ISettingProvider, IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable + public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable { - private static readonly object IndexLock = new object(); internal static Win32[] _win32s { get; set; } internal static UWP.Application[] _uwps { get; set; } internal static Settings _settings { get; set; } @@ -30,17 +30,58 @@ namespace Flow.Launcher.Plugin.Program public Main() { _settingsStorage = new PluginJsonStorage(); - _settings = _settingsStorage.Load(); + } - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () => + public void Save() + { + _settingsStorage.Save(); + _win32Storage.Save(_win32s); + _uwpStorage.Save(_uwps); + } + + public async Task> QueryAsync(Query query, CancellationToken token) + { + Win32[] win32; + UWP.Application[] uwps; + + win32 = _win32s; + uwps = _uwps; + + + var result = await Task.Run(delegate { - _win32Storage = new BinaryStorage("Win32"); - _win32s = _win32Storage.TryLoad(new Win32[] { }); - _uwpStorage = new BinaryStorage("UWP"); - _uwps = _uwpStorage.TryLoad(new UWP.Application[] { }); + return win32.Cast() + .Concat(uwps) + .AsParallel() + .WithCancellation(token) + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, _context.API)) + .Where(r => r?.Score > 0) + .ToList(); + }, token).ConfigureAwait(false); + + return result; + } + + public async Task InitAsync(PluginInitContext context) + { + _context = context; + + await Task.Run(() => + { + _settings = _settingsStorage.Load(); + + Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () => + { + _win32Storage = new BinaryStorage("Win32"); + _win32s = _win32Storage.TryLoad(new Win32[] { }); + _uwpStorage = new BinaryStorage("UWP"); + _uwps = _uwpStorage.TryLoad(new UWP.Application[] { }); + }); + Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); + Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); }); - Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); - Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); + var a = Task.Run(() => { @@ -51,54 +92,22 @@ namespace Flow.Launcher.Plugin.Program var b = Task.Run(() => { if (IsStartupIndexProgramsRequired || !_uwps.Any()) - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUWPPrograms); + Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms); }); - Task.WaitAll(a, b); + await Task.WhenAll(a, b); _settings.LastIndexTime = DateTime.Today; } - public void Save() - { - _settingsStorage.Save(); - _win32Storage.Save(_win32s); - _uwpStorage.Save(_uwps); - } - - public List Query(Query query) - { - Win32[] win32; - UWP.Application[] uwps; - - win32 = _win32s; - uwps = _uwps; - - var result = win32.Cast() - .Concat(uwps) - .AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)) - .Where(r => r?.Score > 0) - .ToList(); - - return result; - } - - public void Init(PluginInitContext context) - { - _context = context; - } - public static void IndexWin32Programs() { var win32S = Win32.All(_settings); _win32s = win32S; - } - public static void IndexUWPPrograms() + public static void IndexUwpPrograms() { var windows10 = new Version(10, 0); var support = Environment.OSVersion.Version.Major >= windows10.Major; @@ -106,16 +115,15 @@ namespace Flow.Launcher.Plugin.Program var applications = support ? UWP.All() : new UWP.Application[] { }; _uwps = applications; - } - public static void IndexPrograms() + public static async Task IndexPrograms() { - var t1 = Task.Run(() => IndexWin32Programs()); + var t1 = Task.Run(IndexWin32Programs); - var t2 = Task.Run(() => IndexUWPPrograms()); + var t2 = Task.Run(IndexUwpPrograms); - Task.WaitAll(t1, t2); + await Task.WhenAll(t1, t2); _settings.LastIndexTime = DateTime.Today; } @@ -145,19 +153,21 @@ namespace Flow.Launcher.Plugin.Program } menuOptions.Add( - new Result - { - Title = _context.API.GetTranslation("flowlauncher_plugin_program_disable_program"), - Action = c => - { - DisableProgram(program); - _context.API.ShowMsg(_context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"), - _context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success_message")); - return false; - }, - IcoPath = "Images/disable.png" - } - ); + new Result + { + Title = _context.API.GetTranslation("flowlauncher_plugin_program_disable_program"), + Action = c => + { + DisableProgram(program); + _context.API.ShowMsg( + _context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"), + _context.API.GetTranslation( + "flowlauncher_plugin_program_disable_dlgtitle_success_message")); + return false; + }, + IcoPath = "Images/disable.png" + } + ); return menuOptions; } @@ -168,21 +178,23 @@ namespace Flow.Launcher.Plugin.Program return; if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = false; + _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = + false; if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = false; + _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = + false; _settings.DisabledProgramSources - .Add( - new Settings.DisabledProgramSource - { - Name = programToDelete.Name, - Location = programToDelete.Location, - UniqueIdentifier = programToDelete.UniqueIdentifier, - Enabled = false - } - ); + .Add( + new Settings.DisabledProgramSource + { + Name = programToDelete.Name, + Location = programToDelete.Location, + UniqueIdentifier = programToDelete.UniqueIdentifier, + Enabled = false + } + ); } public static void StartProcess(Func runProcess, ProcessStartInfo info) @@ -200,9 +212,9 @@ namespace Flow.Launcher.Plugin.Program } } - public void ReloadData() + public async Task ReloadDataAsync() { - IndexPrograms(); + await IndexPrograms(); } } } \ No newline at end of file From 6326d6f3d57e8a5ef6065c210f8d6db7d932ca18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 2 Jan 2021 20:22:07 +0800 Subject: [PATCH 157/442] Startup async --- Flow.Launcher/App.xaml.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 59bdbc896..7417a6f2a 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -45,9 +45,9 @@ namespace Flow.Launcher } } - private void OnStartup(object sender, StartupEventArgs e) + private async void OnStartup(object sender, StartupEventArgs e) { - Stopwatch.Normal("|App.OnStartup|Startup cost", () => + await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => { _portable.PreStartCleanUpAfterPortabilityUpdate(); @@ -70,7 +70,7 @@ namespace Flow.Launcher _mainVM = new MainViewModel(_settings); var window = new MainWindow(_settings, _mainVM); API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet); - PluginManager.InitializePlugins(API); + await PluginManager.InitializePlugins(API); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); Current.MainWindow = window; From 7be2a956dd31aa6d48a263926ea2e70f0a4e437b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 2 Jan 2021 21:20:21 +0800 Subject: [PATCH 158/442] Use cancallationToken.IsCancellationRequested instead of comparing current token with global token --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 3e1a4b516..f9c152046 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -442,7 +442,8 @@ namespace Flow.Launcher.ViewModel async Task QueryTask(PluginPair plugin, Query query, CancellationToken token) { var results = await PluginManager.QueryForPlugin(plugin, query, token); - UpdateResultView(results, plugin.Metadata, query); + if (!currentCancellationToken.IsCancellationRequested) + UpdateResultView(results, plugin.Metadata, query); } }, currentCancellationToken).ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), From 280b98b47a1fbdb5cb8a493339123d9586b54ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 2 Jan 2021 21:23:34 +0800 Subject: [PATCH 159/442] Move the creation of Window later due to async operation --- Flow.Launcher/App.xaml.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 7417a6f2a..0145dfa34 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -68,9 +68,10 @@ namespace Flow.Launcher PluginManager.LoadPlugins(_settings.PluginSettings); _mainVM = new MainViewModel(_settings); - var window = new MainWindow(_settings, _mainVM); API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet); await PluginManager.InitializePlugins(API); + var window = new MainWindow(_settings, _mainVM); + Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); Current.MainWindow = window; From 4cb4aa88ef52dcab60c662a312f8b2e953b40ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 2 Jan 2021 22:00:43 +0800 Subject: [PATCH 160/442] change onstartup name to async --- Flow.Launcher/App.xaml | 2 +- Flow.Launcher/App.xaml.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml index f3347d7fb..18addac73 100644 --- a/Flow.Launcher/App.xaml +++ b/Flow.Launcher/App.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="http://schemas.modernwpf.com/2019" ShutdownMode="OnMainWindowClose" - Startup="OnStartup"> + Startup="OnStartupAsync"> diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 0145dfa34..06bb16e3b 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -45,7 +45,7 @@ namespace Flow.Launcher } } - private async void OnStartup(object sender, StartupEventArgs e) + private async void OnStartupAsync(object sender, StartupEventArgs e) { await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => { From d7805d7a8cbc7eedcee7cc7a62156397f1af5172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 2 Jan 2021 22:01:15 +0800 Subject: [PATCH 161/442] Make Explorer plugin completely async --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 4 +- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 14 ++- .../DirectoryInfo/DirectoryInfoSearch.cs | 6 +- .../Search/SearchManager.cs | 54 ++++++----- .../Search/WindowsIndex/IndexSearch.cs | 90 +++++++++---------- .../ViewModels/SettingsViewModel.cs | 6 ++ 6 files changed, 96 insertions(+), 78 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index c91144825..756ceb2d6 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -151,7 +151,7 @@ namespace Flow.Launcher.Test.Plugins var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var results = searchManager.TopLevelDirectorySearchBehaviour( + var results = searchManager.TopLevelDirectorySearchBehaviourAsync( MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, false, @@ -171,7 +171,7 @@ namespace Flow.Launcher.Test.Plugins var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var results = searchManager.TopLevelDirectorySearchBehaviour( + var results = searchManager.TopLevelDirectorySearchBehaviourAsync( MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, true, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 30a06e882..7b56df691 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -3,11 +3,13 @@ using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Controls; namespace Flow.Launcher.Plugin.Explorer { - public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n + public class Main : ISettingProvider, IAsyncPlugin, ISavable, IContextMenu, IPluginI18n { internal PluginInitContext Context { get; set; } @@ -17,17 +19,21 @@ namespace Flow.Launcher.Plugin.Explorer private IContextMenu contextMenu; + private SearchManager searchManager; + public Control CreateSettingPanel() { return new ExplorerSettings(viewModel); } - public void Init(PluginInitContext context) + public async Task InitAsync(PluginInitContext context) { Context = context; viewModel = new SettingsViewModel(context); + await viewModel.LoadStorage(); Settings = viewModel.Settings; contextMenu = new ContextMenu(Context, Settings); + searchManager = new SearchManager(Settings, Context); } public List LoadContextMenus(Result selectedResult) @@ -35,9 +41,9 @@ namespace Flow.Launcher.Plugin.Explorer return contextMenu.LoadContextMenus(selectedResult); } - public List Query(Query query) + public async Task> QueryAsync(Query query, CancellationToken token) { - return new SearchManager(Settings, Context).Search(query); + return await searchManager.SearchAsync(query, token); } public void Save() diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 02de0eeae..3253b7a7b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Windows; namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { @@ -22,7 +23,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator)) return DirectorySearch(SearchOption.AllDirectories, query, search, criteria); - + return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria); } @@ -57,9 +58,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo try { var directoryInfo = new System.IO.DirectoryInfo(path); - var fileSystemInfos = directoryInfo.GetFileSystemInfos(searchCriteria, searchOption); - foreach (var fileSystemInfo in fileSystemInfos) + foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, searchOption)) { if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 5b50b7fad..6b3a96912 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -5,6 +5,8 @@ using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin.Explorer.Search { @@ -28,20 +30,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search this.settings = settings; } - internal List Search(Query query) + internal async Task> SearchAsync(Query query, CancellationToken token) { var results = new List(); var querySearch = query.Search; if (IsFileContentSearch(query.ActionKeyword)) - return WindowsIndexFileContentSearch(query, querySearch); + return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false); // This allows the user to type the assigned action keyword and only see the list of quick folder links if (settings.QuickFolderAccessLinks.Count > 0 && query.ActionKeyword == settings.SearchActionKeyword && string.IsNullOrEmpty(query.Search)) - return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context); + return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context); var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context); @@ -54,11 +56,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, context); // Query is a location path with a full environment variable, eg. %appdata%\somefolder\ - var isEnvironmentVariablePath = querySearch.Substring(1).Contains("%\\"); + var isEnvironmentVariablePath = querySearch[1..].Contains("%\\"); if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath) { - results.AddRange(WindowsIndexFilesAndFoldersSearch(query, querySearch)); + results.AddRange(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false)); return results; } @@ -72,29 +74,34 @@ namespace Flow.Launcher.Plugin.Explorer.Search return results; var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); - + results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch)); - results.AddRange(TopLevelDirectorySearchBehaviour(WindowsIndexTopLevelFolderSearch, + if (token.IsCancellationRequested) + return null; + + results.AddRange(await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, DirectoryInfoClassSearch, useIndexSearch, query, - locationPath)); + locationPath, + token).ConfigureAwait(false)); return results; } - private List WindowsIndexFileContentSearch(Query query, string querySearchString) + private async Task> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, CancellationToken token) { var queryConstructor = new QueryConstructor(settings); if (string.IsNullOrEmpty(querySearchString)) return new List(); - return indexSearch.WindowsIndexSearch(querySearchString, + return await indexSearch.WindowsIndexSearchAsync(querySearchString, queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.QueryForFileContentSearch, - query); + query, + token).ConfigureAwait(false); } public bool IsFileContentSearch(string actionKeyword) @@ -109,37 +116,40 @@ namespace Flow.Launcher.Plugin.Explorer.Search return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch); } - public List TopLevelDirectorySearchBehaviour( - Func> windowsIndexSearch, + public async Task> TopLevelDirectorySearchBehaviourAsync( + Func>> windowsIndexSearch, Func> directoryInfoClassSearch, bool useIndexSearch, Query query, - string querySearchString) + string querySearchString, + CancellationToken token) { if (!useIndexSearch) return directoryInfoClassSearch(query, querySearchString); - return windowsIndexSearch(query, querySearchString); + return await windowsIndexSearch(query, querySearchString, token); } - private List WindowsIndexFilesAndFoldersSearch(Query query, string querySearchString) + private async Task> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString, CancellationToken token) { var queryConstructor = new QueryConstructor(settings); - return indexSearch.WindowsIndexSearch(querySearchString, + return await indexSearch.WindowsIndexSearchAsync(querySearchString, queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.QueryForAllFilesAndFolders, - query); + query, + token).ConfigureAwait(false); } - - private List WindowsIndexTopLevelFolderSearch(Query query, string path) + + private async Task> WindowsIndexTopLevelFolderSearchAsync(Query query, string path, CancellationToken token) { var queryConstructor = new QueryConstructor(settings); - return indexSearch.WindowsIndexSearch(path, + return await indexSearch.WindowsIndexSearchAsync(path, queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.QueryForTopLevelDirectorySearch, - query); + query, + token).ConfigureAwait(false); } private bool UseWindowsIndexForDirectorySearch(string locationPath) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs index 4f9325c77..5b1d47ef8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs @@ -5,19 +5,13 @@ using System.Collections.Generic; using System.Data.OleDb; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { internal class IndexSearch { - private readonly object _lock = new object(); - - private OleDbConnection conn; - - private OleDbCommand command; - - private OleDbDataReader dataReaderResults; - private readonly ResultManager resultManager; // Reserved keywords in oleDB @@ -28,7 +22,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex resultManager = new ResultManager(context); } - internal List ExecuteWindowsIndexSearch(string indexQueryString, string connectionString, Query query) + internal async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token) { var folderResults = new List(); var fileResults = new List(); @@ -36,47 +30,49 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex try { - using (conn = new OleDbConnection(connectionString)) + using var conn = new OleDbConnection(connectionString); + await conn.OpenAsync(token); + token.ThrowIfCancellationRequested(); + + using var command = new OleDbCommand(indexQueryString, conn); + // Results return as an OleDbDataReader. + using var dataReaderResults = await command.ExecuteReaderAsync(token) as OleDbDataReader; + token.ThrowIfCancellationRequested(); + + if (dataReaderResults.HasRows) { - conn.Open(); - - using (command = new OleDbCommand(indexQueryString, conn)) + while (await dataReaderResults.ReadAsync(token)) { - // Results return as an OleDbDataReader. - using (dataReaderResults = command.ExecuteReader()) + token.ThrowIfCancellationRequested(); + if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) { - if (dataReaderResults.HasRows) - { - while (dataReaderResults.Read()) - { - if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) - { - // # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path - var encodedFragmentPath = dataReaderResults - .GetString(1) - .Replace("#", "%23", StringComparison.OrdinalIgnoreCase); - - var path = new Uri(encodedFragmentPath).LocalPath; + // # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path + var encodedFragmentPath = dataReaderResults + .GetString(1) + .Replace("#", "%23", StringComparison.OrdinalIgnoreCase); - if (dataReaderResults.GetString(2) == "Directory") - { - folderResults.Add(resultManager.CreateFolderResult( - dataReaderResults.GetString(0), - path, - path, - query, true, true)); - } - else - { - fileResults.Add(resultManager.CreateFileResult(path, query, true, true)); - } - } - } + var path = new Uri(encodedFragmentPath).LocalPath; + + if (dataReaderResults.GetString(2) == "Directory") + { + folderResults.Add(resultManager.CreateFolderResult( + dataReaderResults.GetString(0), + path, + path, + query, true, true)); + } + else + { + fileResults.Add(resultManager.CreateFileResult(path, query, true, true)); } } } } } + catch (OperationCanceledException) + { + return new List(); // The source code indicates that without adding members, it won't allocate an array + } catch (InvalidOperationException e) { // Internal error from ExecuteReader(): Connection closed. @@ -91,18 +87,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return results.Concat(folderResults.OrderBy(x => x.Title)).Concat(fileResults.OrderBy(x => x.Title)).ToList(); ; } - internal List WindowsIndexSearch(string searchString, string connectionString, Func constructQuery, Query query) + internal async Task> WindowsIndexSearchAsync(string searchString, string connectionString, + Func constructQuery, Query query, + CancellationToken token) { var regexMatch = Regex.Match(searchString, reservedStringPattern); if (regexMatch.Success) return new List(); - lock (_lock) - { - var constructedQuery = constructQuery(searchString); - return ExecuteWindowsIndexSearch(constructedQuery, connectionString, query); - } + var constructedQuery = constructQuery(searchString); + return await ExecuteWindowsIndexSearchAsync(constructedQuery, connectionString, query, token); + } internal bool PathIsIndexed(string path) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 7fcd77f07..21bc49741 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -3,6 +3,7 @@ using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using System.Diagnostics; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin.Explorer.ViewModels { @@ -21,6 +22,11 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Settings = storage.Load(); } + public Task LoadStorage() + { + return Task.Run(() => Settings = storage.Load()); + } + public void Save() { storage.Save(); From 43cee65c4518b01ca49ba9a267446a390d41501e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 2 Jan 2021 22:21:45 +0800 Subject: [PATCH 162/442] Move PluginManagers to Async Model --- .../Main.cs | 19 +++++++++---------- .../Models/PluginsManifest.cs | 7 +------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index f10f022d7..40579e6e5 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -7,10 +7,11 @@ using System.Windows.Controls; using Flow.Launcher.Infrastructure; using System; using System.Threading.Tasks; +using System.Threading; namespace Flow.Launcher.Plugin.PluginsManager { - public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n, IReloadable + public class Main : ISettingProvider, IAsyncPlugin, ISavable, IContextMenu, IPluginI18n, IAsyncReloadable { internal PluginInitContext Context { get; set; } @@ -29,13 +30,14 @@ namespace Flow.Launcher.Plugin.PluginsManager return new PluginsManagerSettings(viewModel); } - public void Init(PluginInitContext context) + public async Task InitAsync(PluginInitContext context) { Context = context; viewModel = new SettingsViewModel(context); Settings = viewModel.Settings; contextMenu = new ContextMenu(Context); pluginManager = new PluginsManager(Context, Settings); + await pluginManager.UpdateManifest(); lastUpdateTime = DateTime.Now; } @@ -44,7 +46,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return contextMenu.LoadContextMenus(selectedResult); } - public List Query(Query query) + public async Task> QueryAsync(Query query, CancellationToken token) { var search = query.Search.ToLower(); @@ -53,11 +55,8 @@ namespace Flow.Launcher.Plugin.PluginsManager if ((DateTime.Now - lastUpdateTime).TotalHours > 12) // 12 hours { - Task.Run(async () => - { - await pluginManager.UpdateManifest(); - lastUpdateTime = DateTime.Now; - }); + await pluginManager.UpdateManifest(); + lastUpdateTime = DateTime.Now; } return search switch @@ -88,9 +87,9 @@ namespace Flow.Launcher.Plugin.PluginsManager return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); } - public void ReloadData() + public async Task ReloadDataAsync() { - Task.Run(() => pluginManager.UpdateManifest()).Wait(); + await pluginManager.UpdateManifest(); lastUpdateTime = DateTime.Now; } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs index 814e0764d..145aadc98 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -9,12 +9,7 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models { internal class PluginsManifest { - internal List UserPlugins { get; private set; } - - internal PluginsManifest() - { - Task.Run(async () => await DownloadManifest()).Wait(); - } + internal List UserPlugins { get; private set; } = new List(); internal async Task DownloadManifest() { From 69cb8e61479d477a9f0c05a5d7f40b386af6438d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 2 Jan 2021 22:30:56 +0800 Subject: [PATCH 163/442] Reload IAsyncReloadable concurrently --- Flow.Launcher.Core/Plugin/PluginManager.cs | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 239f0499d..2e938127c 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -33,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin /// /// Directories that will hold Flow Launcher plugin directory /// - private static readonly string[] Directories = {Constant.PreinstalledDirectory, DataLocation.PluginsDirectory}; + private static readonly string[] Directories = { Constant.PreinstalledDirectory, DataLocation.PluginsDirectory }; private static void DeletePythonBinding() { @@ -55,18 +55,21 @@ namespace Flow.Launcher.Core.Plugin public static async Task ReloadData() { - foreach(var plugin in AllPlugins) + await Task.WhenAll(AllPlugins.Select(plugin => { - switch (plugin.Plugin) { - case IReloadable p: - p.ReloadData(); - break; - case IAsyncReloadable p: - await p.ReloadDataAsync(); - break; + switch (plugin) + { + case IReloadable p: + p.ReloadData(); // Sync reload means low time consuming + return Task.CompletedTask; + case IAsyncReloadable p: + return p.ReloadDataAsync(); + default: + throw new ArgumentOutOfRangeException(); + } } - } + })); } static PluginManager() @@ -142,7 +145,7 @@ namespace Flow.Launcher.Core.Plugin catch (Exception e) { Log.Exception(nameof(PluginManager), $"Fail to Init plugin: {pair.Metadata.Name}", e); - pair.Metadata.Disabled = true; + pair.Metadata.Disabled = true; failedPlugins.Enqueue(pair); } })); @@ -180,7 +183,7 @@ namespace Flow.Launcher.Core.Plugin if (NonGlobalPlugins.ContainsKey(query.ActionKeyword)) { var plugin = NonGlobalPlugins[query.ActionKeyword]; - return new List {plugin}; + return new List { plugin }; } else { @@ -262,7 +265,7 @@ namespace Flow.Launcher.Core.Plugin var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID); if (pluginPair != null) { - var plugin = (IContextMenu) pluginPair.Plugin; + var plugin = (IContextMenu)pluginPair.Plugin; try { @@ -326,10 +329,10 @@ namespace Flow.Launcher.Core.Plugin { GlobalPlugins.Remove(plugin); } - + if (oldActionkeyword != Query.GlobalPluginWildcardSign) NonGlobalPlugins.Remove(oldActionkeyword); - + plugin.Metadata.ActionKeywords.Remove(oldActionkeyword); } From 6e9e51ec4d674867deb283dd721dd485978cd4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 3 Jan 2021 10:19:05 +0800 Subject: [PATCH 164/442] Error handling for OperationCancelledException in Program plugin --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 39 ++++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 0d693d363..954c238a9 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -47,20 +47,35 @@ namespace Flow.Launcher.Plugin.Program win32 = _win32s; uwps = _uwps; - - var result = await Task.Run(delegate + try { - return win32.Cast() - .Concat(uwps) - .AsParallel() - .WithCancellation(token) - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)) - .Where(r => r?.Score > 0) - .ToList(); - }, token).ConfigureAwait(false); + var result = await Task.Run(delegate + { + try + { + return win32.Cast() + .Concat(uwps) + .AsParallel() + .WithCancellation(token) + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, _context.API)) + .Where(r => r?.Score > 0) + .ToList(); + } + catch (OperationCanceledException) + { + return null; + } + }, token).ConfigureAwait(false); - return result; + token.ThrowIfCancellationRequested(); + + return result; + } + catch (OperationCanceledException) + { + return null; + } } public async Task InitAsync(PluginInitContext context) From 1c200695982e1e0ad170febf0d04498690baea59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 3 Jan 2021 10:19:33 +0800 Subject: [PATCH 165/442] Rebase to Dev --- Flow.Launcher.Core/Plugin/PluginManager.cs | 47 ++++++++++++---------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 2e938127c..b34995ba6 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -197,36 +197,41 @@ namespace Flow.Launcher.Core.Plugin try { var metadata = pair.Metadata; - var milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", - async () => - { - switch (pair.Plugin) - { - case IAsyncPlugin plugin: - results = await plugin.QueryAsync(query, token).ConfigureAwait(false) ?? - new List(); - UpdatePluginMetadata(results, metadata, query); - break; - case IPlugin plugin: - results = await Task.Run(() => plugin.Query(query), token).ConfigureAwait(false) ?? - new List(); - UpdatePluginMetadata(results, metadata, query); - break; - } - }); + + long milliseconds = -1L; + + switch (pair.Plugin) + { + case IAsyncPlugin plugin: + milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", + async () => results = await plugin.QueryAsync(query, token).ConfigureAwait(false)); + break; + case IPlugin plugin: + await Task.Run(() => milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () => + results = plugin.Query(query)), token).ConfigureAwait(false); + break; + default: + throw new ArgumentOutOfRangeException(); + } + token.ThrowIfCancellationRequested(); + UpdatePluginMetadata(results, metadata, query); + metadata.QueryCount += 1; metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2; + token.ThrowIfCancellationRequested(); + } + catch (Exception e) when (e is OperationCanceledException || e is TaskCanceledException) + { + return results = null; } catch (Exception e) { - Log.Exception( - $"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", - e); + Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e); } // null will be fine since the results will only be added into queue if the token hasn't been cancelled - return token.IsCancellationRequested ? results = null : results; + return results; } public static void UpdatePluginMetadata(List results, PluginMetadata metadata, Query query) From 731c3cdcbc23e85b38b3b652d970328adc566675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 3 Jan 2021 10:37:36 +0800 Subject: [PATCH 166/442] Use OperationCancelledException instead of catch Exception and check --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index b34995ba6..0712908bf 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -221,7 +221,7 @@ namespace Flow.Launcher.Core.Plugin metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2; token.ThrowIfCancellationRequested(); } - catch (Exception e) when (e is OperationCanceledException || e is TaskCanceledException) + catch (OperationCanceledException) { return results = null; } From ecf2a7a1f7cd73b787a0aa66bb588e9994e98397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 3 Jan 2021 10:43:05 +0800 Subject: [PATCH 167/442] change plugin type in pluginLoader for Release --- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 8295761b5..b18c07e3c 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -49,7 +49,7 @@ namespace Flow.Launcher.Core.Plugin var plugin = Activator.CreateInstance(type); #else Assembly assembly = null; - IPlugin plugin = null; + object plugin = null; try { From 63e32f1097e6ce061bf431aa6e452f3e47ac19f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 3 Jan 2021 10:52:59 +0800 Subject: [PATCH 168/442] fix testing --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 48 ++++++++++++---------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 756ceb2d6..09c7d9a30 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -7,6 +7,8 @@ using Flow.Launcher.Plugin.SharedCommands; using NUnit.Framework; using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Flow.Launcher.Test.Plugins { @@ -17,15 +19,15 @@ namespace Flow.Launcher.Test.Plugins [TestFixture] public class ExplorerTest { - private List MethodWindowsIndexSearchReturnsZeroResults(Query dummyQuery, string dummyString) + private async Task> MethodWindowsIndexSearchReturnsZeroResultsAsync(Query dummyQuery, string dummyString, CancellationToken dummyToken) { return new List(); } private List MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString) { - return new List - { + return new List + { new Result { Title="Result 1" @@ -64,10 +66,10 @@ namespace Flow.Launcher.Test.Plugins { // Given var queryConstructor = new QueryConstructor(new Settings()); - + //When var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath); - + // Then Assert.IsTrue(queryString == expectedString, $"Expected string: {expectedString}{Environment.NewLine} " + @@ -112,7 +114,7 @@ namespace Flow.Launcher.Test.Plugins } [TestCase("scope='file:'")] - public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString) + public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString) { // Given var queryConstructor = new QueryConstructor(new Settings()); @@ -130,7 +132,7 @@ namespace Flow.Launcher.Test.Plugins "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")] public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString( - string userSearchString, string expectedString) + string userSearchString, string expectedString) { // Given var queryConstructor = new QueryConstructor(new Settings()); @@ -145,18 +147,19 @@ namespace Flow.Launcher.Test.Plugins } [TestCase] - public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() + public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() { // Given var searchManager = new SearchManager(new Settings(), new PluginInitContext()); - + // When - var results = searchManager.TopLevelDirectorySearchBehaviourAsync( - MethodWindowsIndexSearchReturnsZeroResults, - MethodDirectoryInfoClassSearchReturnsTwoResults, - false, + var results = await searchManager.TopLevelDirectorySearchBehaviourAsync( + MethodWindowsIndexSearchReturnsZeroResultsAsync, + MethodDirectoryInfoClassSearchReturnsTwoResults, + false, new Query(), - "string not used"); + "string not used", + default); // Then Assert.IsTrue(results.Count == 2, @@ -165,18 +168,19 @@ namespace Flow.Launcher.Test.Plugins } [TestCase] - public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() + public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() { // Given var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var results = searchManager.TopLevelDirectorySearchBehaviourAsync( - MethodWindowsIndexSearchReturnsZeroResults, + var results = await searchManager.TopLevelDirectorySearchBehaviourAsync( + MethodWindowsIndexSearchReturnsZeroResultsAsync, MethodDirectoryInfoClassSearchReturnsTwoResults, true, new Query(), - "string not used"); + "string not used", + default); // Then Assert.IsTrue(results.Count == 0, @@ -223,7 +227,7 @@ namespace Flow.Launcher.Test.Plugins var query = new Query { ActionKeyword = "doc:", Search = "search term" }; var searchManager = new SearchManager(new Settings(), new PluginInitContext()); - + // When var result = searchManager.IsFileContentSearch(query.ActionKeyword); @@ -250,7 +254,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual check result is {result} {Environment.NewLine}"); } - + [TestCase(@"C:\SomeFolder\SomeApp", true, @"C:\SomeFolder\")] [TestCase(@"C:\SomeFolder\SomeApp\SomeFile", true, @"C:\SomeFolder\SomeApp\")] [TestCase(@"C:\NonExistentFolder\SomeApp", false, "")] @@ -294,7 +298,7 @@ namespace Flow.Launcher.Test.Plugins [TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " + "scope='file:c:\\SomeFolder'")] - public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString) + public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString) { // Given var queryConstructor = new QueryConstructor(new Settings()); @@ -308,7 +312,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {resultString}{Environment.NewLine}"); } - [TestCase("c:\\somefolder\\>somefile","*somefile*")] + [TestCase("c:\\somefolder\\>somefile", "*somefile*")] [TestCase("c:\\somefolder\\somefile", "somefile*")] [TestCase("c:\\somefolder\\", "*")] public void GivenDirectoryInfoSearch_WhenSearchPatternHotKeyIsSearchAll_ThenSearchCriteriaShouldUseCriteriaString(string path, string expectedString) From d369108b0b17d05c300919d7f20e296c5322b731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 3 Jan 2021 20:40:18 +0800 Subject: [PATCH 169/442] Error handling for plugin update --- .../PluginsManager.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 880157a77..4debf5598 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -214,11 +214,19 @@ namespace Flow.Launcher.Plugin.PluginsManager Task.Run(async delegate { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), + Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); + await Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false); + + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), + Context.API.GetTranslation("plugin_pluginsmanager_download_success")); + Install(x.PluginNewUserPlugin, downloadToFilePath); Context.API.RestartApp(); - }); + }).ContinueWith(t => Log.Exception($"|PluginsManager|Update fail for {x.Name}", t.Exception.InnerException), + TaskContinuationOptions.OnlyOnFaulted); return true; } From 8fa1e2e33f4355766282ca6934e3a279b8a3b8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 3 Jan 2021 20:49:14 +0800 Subject: [PATCH 170/442] add one more message --- .../Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 4debf5598..9cef704a6 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -225,8 +225,12 @@ namespace Flow.Launcher.Plugin.PluginsManager Install(x.PluginNewUserPlugin, downloadToFilePath); Context.API.RestartApp(); - }).ContinueWith(t => Log.Exception($"|PluginsManager|Update fail for {x.Name}", t.Exception.InnerException), - TaskContinuationOptions.OnlyOnFaulted); + }).ContinueWith(t => + { + Log.Exception($"|PluginsManager|Update fail for {x.Name}", t.Exception.InnerException); + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), x.Name)); + }, TaskContinuationOptions.OnlyOnFaulted); return true; } From d35a3bb43d05cf19b1fd46ef3577376941b7ed44 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 4 Jan 2021 06:40:45 +1100 Subject: [PATCH 171/442] version bump for PluginsManager --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index f5bbf3f6b..ef2c1255a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.4.0", + "Version": "1.4.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From 6287358275e01457fabf0e2482343836a80bb9dd Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 4 Jan 2021 06:41:05 +1100 Subject: [PATCH 172/442] update error message --- Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 9cef704a6..724ddf20d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -227,7 +227,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.RestartApp(); }).ContinueWith(t => { - Log.Exception($"|PluginsManager|Update fail for {x.Name}", t.Exception.InnerException); + Log.Exception("PluginsManager", $"Update failed for {x.Name}", t.Exception.InnerException, "RequestUpdate"); Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), x.Name)); }, TaskContinuationOptions.OnlyOnFaulted); From a8dc1599d746d88c8ce28ac551abb0e02bf6a90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 4 Jan 2021 10:28:33 +0800 Subject: [PATCH 173/442] Add Http Error Handling --- Flow.Launcher.Infrastructure/Http/Http.cs | 44 +++++++++++++++++------ 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 8e2832690..b796d807c 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -91,7 +91,7 @@ namespace Flow.Launcher.Infrastructure.Http /// /// Asynchrously get the result as string from url. - /// When supposing the result is long and large, try using GetStreamAsync to avoid reading as string + /// When supposing the result larger than 83kb, try using GetStreamAsync to avoid reading as string /// /// /// @@ -101,19 +101,33 @@ namespace Flow.Launcher.Infrastructure.Http return GetAsync(new Uri(url.Replace("#", "%23"))); } + /// + /// Asynchrously get the result as string from url. + /// When supposing the result larger than 83kb, try using GetStreamAsync to avoid reading as string + /// + /// + /// public static async Task GetAsync([NotNull] Uri url) { Log.Debug($"|Http.Get|Url <{url}>"); - using var response = await client.GetAsync(url); - var content = await response.Content.ReadAsStringAsync(); - if (response.StatusCode == HttpStatusCode.OK) + try { - return content; + using var response = await client.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + return content; + } + else + { + throw new HttpRequestException( + $"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); + } } - else + catch (HttpRequestException e) { - throw new HttpRequestException( - $"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>"); + Log.Exception("Infrastructure.Http", "Http Request Error", e, "GetAsync"); + throw; } } @@ -124,9 +138,17 @@ namespace Flow.Launcher.Infrastructure.Http /// public static async Task GetStreamAsync([NotNull] string url) { - Log.Debug($"|Http.Get|Url <{url}>"); - var response = await client.GetAsync(url); - return await response.Content.ReadAsStreamAsync(); + try + { + Log.Debug($"|Http.Get|Url <{url}>"); + var response = await client.GetAsync(url); + return await response.Content.ReadAsStreamAsync(); + } + catch (HttpRequestException e) + { + Log.Exception("Infrastructure.Http", "Http Request Error", e, "GetStreamAsync"); + throw; + } } } } From bc63b9ebfa100426176603ba6c7009b72dd3502e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 4 Jan 2021 10:31:05 +0800 Subject: [PATCH 174/442] Change Download to DownloadAsync Add error handling for download as well --- Flow.Launcher.Infrastructure/Http/Http.cs | 22 +++++++++++++------ .../PluginsManager.cs | 4 ++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index b796d807c..78fa099c9 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -75,17 +75,25 @@ namespace Flow.Launcher.Infrastructure.Http }; } - public static async Task Download([NotNull] string url, [NotNull] string filePath) + public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath) { - using var response = await client.GetAsync(url); - if (response.StatusCode == HttpStatusCode.OK) + try { - await using var fileStream = new FileStream(filePath, FileMode.CreateNew); - await response.Content.CopyToAsync(fileStream); + using var response = await client.GetAsync(url); + if (response.StatusCode == HttpStatusCode.OK) + { + await using var fileStream = new FileStream(filePath, FileMode.CreateNew); + await response.Content.CopyToAsync(fileStream); + } + else + { + throw new HttpRequestException($"Error code <{response.StatusCode}> returned from <{url}>"); + } } - else + catch (HttpRequestException e) { - throw new HttpRequestException($"Error code <{response.StatusCode}> returned from <{url}>"); + Log.Exception("Infrastructure.Http", "Http Request Error", e, "DownloadAsync"); + throw; } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 724ddf20d..68df5bc1f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -127,7 +127,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); - await Http.Download(plugin.UrlDownload, filePath).ConfigureAwait(false); + await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_download_success")); @@ -217,7 +217,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); - await Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false); + await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false); Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.GetTranslation("plugin_pluginsmanager_download_success")); From fb640b68c8e5a49e105e0666ce77e057c1a43735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 4 Jan 2021 11:05:59 +0800 Subject: [PATCH 175/442] Use ValueTask instead of Task --- Flow.Launcher/ViewModel/ResultViewModel.cs | 25 ++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 00a0e1ae5..20d32890b 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; @@ -12,7 +13,7 @@ namespace Flow.Launcher.ViewModel { public class ResultViewModel : BaseModel { - public class LazyAsync : Lazy> + public class LazyAsync : Lazy> { private T defaultValue; @@ -23,21 +24,27 @@ namespace Flow.Launcher.ViewModel { if (!IsValueCreated) { - base.Value.ContinueWith(_ => - { - _updateCallback(); - }); + _ = Exercute(); return defaultValue; } - + if (!base.Value.IsCompleted || base.Value.IsFaulted) return defaultValue; return base.Value.Result; + + // If none of the variables captured by the local function are captured by other lambdas + // , the compiler can avoid heap allocations. + async ValueTask Exercute() + { + await base.Value.ConfigureAwait(false); + _updateCallback(); + } + } } - public LazyAsync(Func> factory, T defaultValue, Action updateCallback) : base(factory) + public LazyAsync(Func> factory, T defaultValue, Action updateCallback) : base(factory) { if (defaultValue != null) { @@ -55,7 +62,7 @@ namespace Flow.Launcher.ViewModel Result = result; Image = new LazyAsync( - SetImage, + SetImage, ImageLoader.DefaultImage, () => { @@ -82,7 +89,7 @@ namespace Flow.Launcher.ViewModel public LazyAsync Image { get; set; } - private async Task SetImage() + private async ValueTask SetImage() { var imagePath = Result.IcoPath; if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) From 7967844cf381563c362f2d91bcac73342b3f5237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 4 Jan 2021 11:10:02 +0800 Subject: [PATCH 176/442] make defaultValue readonly and edit comment --- Flow.Launcher/ViewModel/ResultViewModel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 20d32890b..d1b03f911 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -15,7 +15,7 @@ namespace Flow.Launcher.ViewModel { public class LazyAsync : Lazy> { - private T defaultValue; + private readonly T defaultValue; private readonly Action _updateCallback; public new T Value @@ -24,7 +24,7 @@ namespace Flow.Launcher.ViewModel { if (!IsValueCreated) { - _ = Exercute(); + _ = Exercute(); // manually use callback strategy return defaultValue; } @@ -34,8 +34,8 @@ namespace Flow.Launcher.ViewModel return base.Value.Result; - // If none of the variables captured by the local function are captured by other lambdas - // , the compiler can avoid heap allocations. + // If none of the variables captured by the local function are captured by other lambdas, + // the compiler can avoid heap allocations. async ValueTask Exercute() { await base.Value.ConfigureAwait(false); From fc015245b3392664f4b630a6a10a68b85d05bc2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 4 Jan 2021 16:22:48 +0800 Subject: [PATCH 177/442] Change the way checking successfuly --- Flow.Launcher/ViewModel/ResultViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index d1b03f911..9eece1a97 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -29,7 +29,7 @@ namespace Flow.Launcher.ViewModel return defaultValue; } - if (!base.Value.IsCompleted || base.Value.IsFaulted) + if (!base.Value.IsCompletedSuccessfully) return defaultValue; return base.Value.Result; From 6703e0d137b3fcd03c8fdb16644df7fa5645b24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 4 Jan 2021 16:31:48 +0800 Subject: [PATCH 178/442] Return default image when loading UWP icon fail --- Flow.Launcher/ViewModel/ResultViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 9eece1a97..4c65f2b9f 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -68,7 +68,7 @@ namespace Flow.Launcher.ViewModel { OnPropertyChanged(nameof(Image)); }); - } + } Settings = settings; } @@ -101,7 +101,7 @@ namespace Flow.Launcher.ViewModel catch (Exception e) { Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); - imagePath = Constant.MissingImgIcon; + return ImageLoader.DefaultImage; } } From e790e9474e8fab545325eaa685314ea99821d470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 5 Jan 2021 16:11:38 +0800 Subject: [PATCH 179/442] Add Plugin Priority Settings --- .../UserSettings/PluginSettings.cs | 5 +- Flow.Launcher.Plugin/PluginMetadata.cs | 5 +- Flow.Launcher/PriorityChangeWindow.xaml | 43 ++++++++++++ Flow.Launcher/PriorityChangeWindow.xaml.cs | 69 +++++++++++++++++++ Flow.Launcher/SettingWindow.xaml | 8 ++- Flow.Launcher/SettingWindow.xaml.cs | 12 +++- Flow.Launcher/ViewModel/MainViewModel.cs | 6 +- Flow.Launcher/ViewModel/PluginViewModel.cs | 7 ++ .../ViewModel/SettingWindowViewModel.cs | 1 + .../Flow.Launcher.Plugin.Calculator/Main.cs | 2 +- 10 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 Flow.Launcher/PriorityChangeWindow.xaml create mode 100644 Flow.Launcher/PriorityChangeWindow.xaml.cs diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index ccd9beb86..29bc11480 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -31,6 +31,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings metadata.ActionKeyword = settings.ActionKeywords[0]; } metadata.Disabled = settings.Disabled; + metadata.Priority = settings.Priority; } else { @@ -40,7 +41,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings Name = metadata.Name, Version = metadata.Version, ActionKeywords = metadata.ActionKeywords, - Disabled = metadata.Disabled + Disabled = metadata.Disabled, + Priority = metadata.Priority }; } } @@ -52,6 +54,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public string Name { get; set; } public string Version { get; set; } public List ActionKeywords { get; set; } // a reference of the action keywords from plugin manager + public int Priority { get; set; } /// /// Used only to save the state of the plugin in settings diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index d81b442e2..8bcbcd231 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -37,11 +37,14 @@ namespace Flow.Launcher.Plugin public List ActionKeywords { get; set; } public string IcoPath { get; set;} - + public override string ToString() { return Name; } + [JsonIgnore] + public int Priority { get; set; } + /// /// Init time include both plugin load time and init time diff --git a/Flow.Launcher/PriorityChangeWindow.xaml b/Flow.Launcher/PriorityChangeWindow.xaml new file mode 100644 index 000000000..51509f1ab --- /dev/null +++ b/Flow.Launcher/PriorityChangeWindow.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/PriorityChangeWindow.xaml.cs b/Flow.Launcher/PriorityChangeWindow.xaml.cs new file mode 100644 index 000000000..01c6454c9 --- /dev/null +++ b/Flow.Launcher/PriorityChangeWindow.xaml.cs @@ -0,0 +1,69 @@ +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Core.Resource; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Flow.Launcher.ViewModel; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace Flow.Launcher +{ + /// + /// PriorityChangeWindow.xaml 的交互逻辑 + /// + public partial class PriorityChangeWindow : Window + { + private readonly PluginPair plugin; + private Settings settings; + private readonly Internationalization translater = InternationalizationManager.Instance; + private readonly PluginViewModel pluginViewModel; + + public PriorityChangeWindow(string pluginId, Settings settings, PluginViewModel pluginViewModel) + { + InitializeComponent(); + plugin = PluginManager.GetPluginForId(pluginId); + this.settings = settings; + this.pluginViewModel = pluginViewModel; + if (plugin == null) + { + MessageBox.Show(translater.GetTranslation("cannotFindSpecifiedPlugin")); + Close(); + } + } + + private void BtnCancel_OnClick(object sender, RoutedEventArgs e) + { + Close(); + } + + private void btnDone_OnClick(object sender, RoutedEventArgs e) + { + if (int.TryParse(tbAction.Text.Trim(), out var newPriority)) + { + pluginViewModel.ChangePriority(newPriority); + Close(); + } + else + { + string msg = "Please provide an valid integer";// translater.GetTranslation("newActionKeywordsHasBeenAssigned"); + MessageBox.Show(msg); + } + + } + + private void PriorityChangeWindow_Loaded(object sender, RoutedEventArgs e) + { + OldPriority.Text = pluginViewModel.Priority.ToString(); + tbAction.Focus(); + } + } +} diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index e47f0e779..b000db20e 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -172,10 +172,14 @@ - + + + Margin="5 0 0 0"/> PluginPair.Metadata.InitTime.ToString() + "ms"; public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms"; public string ActionKeywordsText => string.Join(Query.ActionKeywordSeperater, PluginPair.Metadata.ActionKeywords); + public int Priority => PluginPair.Metadata.Priority; public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword) { @@ -34,6 +35,12 @@ namespace Flow.Launcher.ViewModel OnPropertyChanged(nameof(ActionKeywordsText)); } + public void ChangePriority(int newPriority) + { + PluginPair.Metadata.Priority = newPriority; + OnPropertyChanged(nameof(Priority)); + } + public bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); } } diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index c122f8037..4ebf898ea 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -88,6 +88,7 @@ namespace Flow.Launcher.ViewModel var id = vm.PluginPair.Metadata.ID; Settings.PluginSettings.Plugins[id].Disabled = vm.PluginPair.Metadata.Disabled; + Settings.PluginSettings.Plugins[id].Priority = vm.Priority; } PluginManager.Save(); diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 949911229..5b23ceacc 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -91,7 +91,7 @@ namespace Flow.Launcher.Plugin.Caculator }; } } - catch + catch (Exception) { // ignored } From a2f741b376d5c00da6ad51e0d800e5f3ccf24d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 09:59:20 +0800 Subject: [PATCH 180/442] change bitmap scaling mode to Fant (High Quality) to make the search icon looks better. --- Flow.Launcher/MainWindow.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 07bb96339..b6782cb60 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -92,7 +92,7 @@ - + Date: Wed, 6 Jan 2021 10:34:08 +0800 Subject: [PATCH 181/442] Use SVG for search icon --- Flow.Launcher.Infrastructure/Constant.cs | 2 +- Flow.Launcher/Flow.Launcher.csproj | 1 + Flow.Launcher/Images/mainsearch.svg | 10 ++++++++++ Flow.Launcher/MainWindow.xaml | 9 +++++---- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 5 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 Flow.Launcher/Images/mainsearch.svg diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 3dba35f8d..de6ed1b29 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -30,7 +30,7 @@ namespace Flow.Launcher.Infrastructure public static string PythonPath; - public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.png"; + public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.svg"; public const string DefaultTheme = "Darker"; diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 39b2087aa..0f8e6a767 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -82,6 +82,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Flow.Launcher/Images/mainsearch.svg b/Flow.Launcher/Images/mainsearch.svg new file mode 100644 index 000000000..5d28abdb3 --- /dev/null +++ b/Flow.Launcher/Images/mainsearch.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index b6782cb60..c9db58385 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -6,6 +6,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:converters="clr-namespace:Flow.Launcher.Converters" + xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" mc:Ignorable="d" Title="Flow Launcher" Topmost="True" @@ -68,8 +69,8 @@ Margin="18,0,56,0"> - - + + @@ -81,7 +82,7 @@ AllowDrop="True" Visibility="Visible" Background="Transparent" - Margin="18,0,56,0"> + Margin="18,1,0,1" HorizontalAlignment="Left" Width="660"> @@ -92,7 +93,7 @@ - + ImageLoader.Load(Constant.QueryTextBoxIconImagePath); + public string Image => Constant.QueryTextBoxIconImagePath; #endregion From 0dd6a982ad93affc9cc2d3aae8dd7a4ba31bc176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 15:51:52 +0800 Subject: [PATCH 182/442] change setting window to svg as well --- Flow.Launcher/SettingWindow.xaml | 3 ++- Flow.Launcher/ViewModel/SettingWindowViewModel.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index e47f0e779..121b062ee 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -8,6 +8,7 @@ xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:ui="http://schemas.modernwpf.com/2019" + xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" x:Class="Flow.Launcher.SettingWindow" mc:Ignorable="d" Icon="Images\app.png" @@ -252,7 +253,7 @@ Text="{DynamicResource hiThere}" IsReadOnly="True" Style="{DynamicResource QueryBoxStyle}" Margin="18 0 56 0" /> - + diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index c122f8037..c43167e09 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -438,7 +438,7 @@ namespace Flow.Launcher.ViewModel } } - public ImageSource ThemeImage => ImageLoader.Load(Constant.QueryTextBoxIconImagePath); + public string ThemeImage => Constant.QueryTextBoxIconImagePath; #endregion From 3bfd900f054687dee90a9c9670ff55e096000116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 16:10:52 +0800 Subject: [PATCH 183/442] revert change for oneway for mutli binding --- Flow.Launcher/MainWindow.xaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index c9db58385..4373c0f0c 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -69,8 +69,8 @@ Margin="18,0,56,0"> - - + + @@ -93,7 +93,8 @@ - + Date: Wed, 6 Jan 2021 16:46:38 +0800 Subject: [PATCH 184/442] remove unintended change and change the binding mode to default --- Flow.Launcher/MainWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 4373c0f0c..998bc3e9a 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -82,7 +82,7 @@ AllowDrop="True" Visibility="Visible" Background="Transparent" - Margin="18,1,0,1" HorizontalAlignment="Left" Width="660"> + Margin="18,0,56,0" HorizontalAlignment="Left" Width="660"> @@ -93,7 +93,7 @@ - Date: Wed, 6 Jan 2021 17:00:16 +0800 Subject: [PATCH 185/442] change autogenerated summary to english --- Flow.Launcher/PriorityChangeWindow.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/PriorityChangeWindow.xaml.cs b/Flow.Launcher/PriorityChangeWindow.xaml.cs index 01c6454c9..f04ad110c 100644 --- a/Flow.Launcher/PriorityChangeWindow.xaml.cs +++ b/Flow.Launcher/PriorityChangeWindow.xaml.cs @@ -18,7 +18,7 @@ using System.Windows.Shapes; namespace Flow.Launcher { /// - /// PriorityChangeWindow.xaml 的交互逻辑 + /// Interaction Logic of PriorityChangeWindow.xaml /// public partial class PriorityChangeWindow : Window { From 4973f2d979b2da3e6a66245b828105e6154676c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 17:05:10 +0800 Subject: [PATCH 186/442] remove png file --- Flow.Launcher/Images/mainsearch.png | Bin 63073 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Flow.Launcher/Images/mainsearch.png diff --git a/Flow.Launcher/Images/mainsearch.png b/Flow.Launcher/Images/mainsearch.png deleted file mode 100644 index ac5492fa62dfa1d7bf207c60823b2f9fa6f4b622..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63073 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 From 833eccc25a9ff09f7ec4bbdbecd26904bbb7ee9f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 6 Jan 2021 20:09:41 +1100 Subject: [PATCH 187/442] archive mainsearch.png --- Doc/mainsearch.png | Bin 0 -> 63073 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Doc/mainsearch.png diff --git a/Doc/mainsearch.png b/Doc/mainsearch.png new file mode 100644 index 0000000000000000000000000000000000000000..ac5492fa62dfa1d7bf207c60823b2f9fa6f4b622 GIT binary patch literal 63073 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 literal 0 HcmV?d00001 From e85b766f5781560facc2f3ccb89266f7bab70730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 17:17:24 +0800 Subject: [PATCH 188/442] remove extra width --- Flow.Launcher/MainWindow.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 998bc3e9a..a196f369f 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -82,7 +82,7 @@ AllowDrop="True" Visibility="Visible" Background="Transparent" - Margin="18,0,56,0" HorizontalAlignment="Left" Width="660"> + Margin="18,0,56,0" HorizontalAlignment="Left"> From b2fcd98f2c4fa66ef211522a3a5f0f525d8dd4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 17:18:21 +0800 Subject: [PATCH 189/442] remove extra horizontalalignment --- Flow.Launcher/MainWindow.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index a196f369f..a2cfe569d 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -82,7 +82,7 @@ AllowDrop="True" Visibility="Visible" Background="Transparent" - Margin="18,0,56,0" HorizontalAlignment="Left"> + Margin="18,0,56,0"> From 8569b44247cbc066b2916c3ea1371a5a44751746 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 6 Jan 2021 20:23:38 +1100 Subject: [PATCH 190/442] search preview consistent --- Flow.Launcher/SettingWindow.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 121b062ee..e467d9a64 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -253,7 +253,8 @@ Text="{DynamicResource hiThere}" IsReadOnly="True" Style="{DynamicResource QueryBoxStyle}" Margin="18 0 56 0" /> - + From 182cdd199a564349df5ed146200514c8b2a80631 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 6 Jan 2021 20:24:36 +1100 Subject: [PATCH 191/442] fix typo --- Flow.Launcher/Languages/en.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index adb49b65d..6ee28e3ba 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -72,7 +72,7 @@ Are you sure you want to delete {0} plugin hotkey? Query window shadow effect Shadow effect has a substantial usage of GPU. - Not recommended if you computer performance is limited. + Not recommended if your computer performance is limited. HTTP Proxy From 6b597f7516205a5057cfd4b80ab29a723c10c2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 17:42:46 +0800 Subject: [PATCH 192/442] throw exception if debug for Log.exception(className, message, exception) --- Flow.Launcher.Infrastructure/Logger/Log.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 289ec5d68..d485f964b 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -50,14 +50,18 @@ namespace Flow.Launcher.Infrastructure.Logger return valid; } - + [MethodImpl(MethodImplOptions.Synchronized)] public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "") { +#if DEBUG + throw exception; +#else var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName); ExceptionInternal(classNameWithMethod, message, exception); +#endif } private static string CheckClassAndMessageAndReturnFullClassWithMethod(string className, string message, From 0d24aba74fbde39e894be9246e9ae297841e6440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 17:43:48 +0800 Subject: [PATCH 193/442] Use actionkeyword comparison to determine whether delay querying. --- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 1ca44c8b9..3ecf30139 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -437,7 +437,7 @@ namespace Flow.Launcher.ViewModel var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => { - if (plugins.Count > 1) + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { // Wait 45 millisecond for query change in global query // if query changes, return so that it won't be calculated @@ -468,7 +468,7 @@ namespace Flow.Launcher.ViewModel var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); } - catch(Exception e) + catch (Exception e) { Log.Exception("MainViewModel", $"Exception when querying the plugin {plugin.Metadata.Name}", e, "QueryResults"); } From 6f43d4fdf85d0426fba0a89caf7167ac853f3d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 17:51:55 +0800 Subject: [PATCH 194/442] remove extra space --- Flow.Launcher.Plugin/PluginMetadata.cs | 1 + Flow.Launcher/PriorityChangeWindow.xaml.cs | 2 +- Flow.Launcher/SettingWindow.xaml.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index 8bcbcd231..9eed86025 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -42,6 +42,7 @@ namespace Flow.Launcher.Plugin { return Name; } + [JsonIgnore] public int Priority { get; set; } diff --git a/Flow.Launcher/PriorityChangeWindow.xaml.cs b/Flow.Launcher/PriorityChangeWindow.xaml.cs index f04ad110c..0c1dad1ed 100644 --- a/Flow.Launcher/PriorityChangeWindow.xaml.cs +++ b/Flow.Launcher/PriorityChangeWindow.xaml.cs @@ -66,4 +66,4 @@ namespace Flow.Launcher tbAction.Focus(); } } -} +} \ No newline at end of file diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs index 2402ccd24..a922b4d67 100644 --- a/Flow.Launcher/SettingWindow.xaml.cs +++ b/Flow.Launcher/SettingWindow.xaml.cs @@ -292,4 +292,4 @@ namespace Flow.Launcher } } -} +} \ No newline at end of file From 3ba1bf6ab0157ac48d13f62e2c85a74a4195b750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 6 Jan 2021 18:00:45 +0800 Subject: [PATCH 195/442] Use l18n instead of static string --- Flow.Launcher/Languages/en.xaml | 6 ++++++ Flow.Launcher/PriorityChangeWindow.xaml | 7 ++++--- Flow.Launcher/PriorityChangeWindow.xaml.cs | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index adb49b65d..a275a2c16 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -43,6 +43,8 @@ Action keyword: Current action keyword: New action keyword: + Current Priority: + New Priority: Plugin Directory Author Init time: @@ -104,6 +106,10 @@ Release Notes + + Higher the number, the closer the result will be to the top. If you want the results to be lower than any other plugin's, provide a negative number + Please provide an valid integer for Priority! + Old Action Keyword New Action Keyword diff --git a/Flow.Launcher/PriorityChangeWindow.xaml b/Flow.Launcher/PriorityChangeWindow.xaml index 51509f1ab..54e5c02cc 100644 --- a/Flow.Launcher/PriorityChangeWindow.xaml +++ b/Flow.Launcher/PriorityChangeWindow.xaml @@ -19,18 +19,19 @@ + HorizontalAlignment="Left" Text="{DynamicResource currentPriority}" /> + HorizontalAlignment="Left" Text="{DynamicResource newPriority}" /> + Text="{DynamicResource priority_tips}" TextWrapping="Wrap" + Margin="0,0,20,0"/> /// /// - public static async Task GetStreamAsync([NotNull] string url) + public static async Task GetStreamAsync([NotNull] string url, CancellationToken token = default) { Log.Debug($"|Http.Get|Url <{url}>"); - var response = await client.GetAsync(url); + var response = await client.GetAsync(url, token); + if (token.IsCancellationRequested) + return Stream.Null; return await response.Content.ReadAsStreamAsync(); } } From a4edbc2cb9a97f7a82b51fca4f0b3fe9c5faf642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 11:09:08 +0800 Subject: [PATCH 208/442] Move WebSearch to Async model --- .../Flow.Launcher.Plugin.WebSearch/Main.cs | 84 +++++++++---------- .../SuggestionSources/Baidu.cs | 5 +- .../SuggestionSources/Bing.cs | 7 +- .../SuggestionSources/Google.cs | 5 +- .../SuggestionSources/SuggestionSource.cs | 3 +- 5 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index 3c4d4c67d..a573674ec 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; @@ -13,14 +14,12 @@ using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.WebSearch { - public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IResultUpdated + public class Main : IAsyncPlugin, ISettingProvider, IPluginI18n, ISavable, IResultUpdated { private PluginInitContext _context; private readonly Settings _settings; private readonly SettingsViewModel _viewModel; - private CancellationTokenSource _updateSource; - private CancellationToken _updateToken; internal const string Images = "Images"; internal static string DefaultImagesDirectory; @@ -33,7 +32,7 @@ namespace Flow.Launcher.Plugin.WebSearch _viewModel.Save(); } - public List Query(Query query) + public async Task> QueryAsync(Query query, CancellationToken token) { if (FilesFolders.IsLocationPathString(query.Search)) return new List(); @@ -41,11 +40,7 @@ namespace Flow.Launcher.Plugin.WebSearch var searchSourceList = new List(); var results = new List(); - _updateSource?.Cancel(); - _updateSource = new CancellationTokenSource(); - _updateToken = _updateSource.Token; - - _settings.SearchSources.Where(o => (o.ActionKeyword == query.ActionKeyword || o.ActionKeyword == SearchSourceGlobalPluginWildCardSign) + _settings.SearchSources.Where(o => (o.ActionKeyword == query.ActionKeyword || o.ActionKeyword == SearchSourceGlobalPluginWildCardSign) && o.Enabled) .ToList() .ForEach(x => searchSourceList.Add(x)); @@ -94,49 +89,45 @@ namespace Flow.Launcher.Plugin.WebSearch }; results.Add(result); - ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs - { - Results = results, - Query = query - }); - - UpdateResultsFromSuggestion(results, keyword, subtitle, searchSource, query); } + + ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs + { + Results = results, + Query = query + }); + + await UpdateResultsFromSuggestionAsync(results, keyword, subtitle, searchSource, query, token).ConfigureAwait(false); } } return results; } - private void UpdateResultsFromSuggestion(List results, string keyword, string subtitle, - SearchSource searchSource, Query query) + private async Task UpdateResultsFromSuggestionAsync(List results, string keyword, string subtitle, + SearchSource searchSource, Query query, CancellationToken token) { if (_settings.EnableSuggestion) { - const int waittime = 300; - var task = Task.Run(async () => - { - var suggestions = await Suggestions(keyword, subtitle, searchSource); - results.AddRange(suggestions); - }, _updateToken); + var suggestions = await SuggestionsAsync(keyword, subtitle, searchSource, token).ConfigureAwait(false); + results.AddRange(suggestions); - if (!task.Wait(waittime)) + token.ThrowIfCancellationRequested(); + + ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs { - task.ContinueWith(_ => ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs - { - Results = results, - Query = query - }), _updateToken); - } + Results = results, + Query = query + }); } } - private async Task> Suggestions(string keyword, string subtitle, SearchSource searchSource) + private async Task> SuggestionsAsync(string keyword, string subtitle, SearchSource searchSource, CancellationToken token) { var source = _settings.SelectedSuggestion; if (source != null) { - var suggestions = await source.Suggestions(keyword); + var suggestions = await source.Suggestions(keyword, token); var resultsFromSuggestion = suggestions.Select(o => new Result { Title = o, @@ -169,19 +160,24 @@ namespace Flow.Launcher.Plugin.WebSearch _settings = _viewModel.Settings; } - public void Init(PluginInitContext context) + public Task InitAsync(PluginInitContext context) { - _context = context; - var pluginDirectory = _context.CurrentPluginMetadata.PluginDirectory; - var bundledImagesDirectory = Path.Combine(pluginDirectory, Images); - - // Default images directory is in the WebSearch's application folder - DefaultImagesDirectory = Path.Combine(pluginDirectory, Images); - Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory); + return Task.Run(Init); - // Custom images directory is in the WebSearch's data location folder - var name = Path.GetFileNameWithoutExtension(_context.CurrentPluginMetadata.ExecuteFileName); - CustomImagesDirectory = Path.Combine(DataLocation.PluginSettingsDirectory, name, "CustomIcons"); + void Init() + { + _context = context; + var pluginDirectory = _context.CurrentPluginMetadata.PluginDirectory; + var bundledImagesDirectory = Path.Combine(pluginDirectory, Images); + + // Default images directory is in the WebSearch's application folder + DefaultImagesDirectory = Path.Combine(pluginDirectory, Images); + Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory); + + // Custom images directory is in the WebSearch's data location folder + var name = Path.GetFileNameWithoutExtension(_context.CurrentPluginMetadata.ExecuteFileName); + CustomImagesDirectory = Path.Combine(DataLocation.PluginSettingsDirectory, name, "CustomIcons"); + }; } #region ISettingProvider Members diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index 6772acf82..0c6d95b4f 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json.Linq; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; using System.Net.Http; +using System.Threading; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { @@ -16,14 +17,14 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { private readonly Regex _reg = new Regex("window.baidu.sug\\((.*)\\)"); - public override async Task> Suggestions(string query) + public override async Task> Suggestions(string query, CancellationToken token) { string result; try { const string api = "http://suggestion.baidu.com/su?json=1&wd="; - result = await Http.GetAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); + result = await Http.GetAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); } catch (HttpRequestException e) { diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index 9c4746711..991e45267 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -9,19 +9,20 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Text.Json; using System.Linq; +using System.Threading; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { class Bing : SuggestionSource { - public override async Task> Suggestions(string query) + public override async Task> Suggestions(string query, CancellationToken token) { Stream resultStream; try { const string api = "https://api.bing.com/qsonhs.aspx?q="; - resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); + resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); } catch (HttpRequestException e) { @@ -29,7 +30,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources return new List(); } - if (resultStream.Length == 0) return new List(); + if (resultStream.Length == 0) return new List(); // this handles the cancellation JsonElement json; try diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 5b9538091..d034679af 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -8,18 +8,19 @@ using Newtonsoft.Json.Linq; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; using System.Net.Http; +using System.Threading; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { public class Google : SuggestionSource { - public override async Task> Suggestions(string query) + public override async Task> Suggestions(string query, CancellationToken token) { string result; try { const string api = "https://www.google.com/complete/search?output=chrome&q="; - result = await Http.GetAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); + result = await Http.GetAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); } catch (HttpRequestException e) { diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs index d6d89415f..bf444a2f7 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { public abstract class SuggestionSource { - public abstract Task> Suggestions(string query); + public abstract Task> Suggestions(string query, CancellationToken token); } } \ No newline at end of file From f72b716fb4a3b9de8dab6a336825b38bdc5ae338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 11:20:14 +0800 Subject: [PATCH 209/442] use string.empty instead of null --- Flow.Launcher.Infrastructure/Http/Http.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 173be8422..4abf0e832 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -113,7 +113,7 @@ namespace Flow.Launcher.Infrastructure.Http Log.Debug($"|Http.Get|Url <{url}>"); using var response = await client.GetAsync(url, token); if (token.IsCancellationRequested) - return null; + return string.Empty; var content = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.OK) { From e1f715e6d607455bfbdd78f0e626ae1a5089f33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 16:52:05 +0800 Subject: [PATCH 210/442] change commnet --- Flow.Launcher.Infrastructure/UserSettings/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index bcfe298a9..769237bcb 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -102,7 +102,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected; - // Order defaults to 0 or -1, so 1 will let this property appear last + // This needs to be loaded last by staying at the bottom public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); } From 7501a7e7e7ee597a39838ffd57ccc7f959083b62 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 7 Jan 2021 20:40:22 +1100 Subject: [PATCH 211/442] version bump --- SolutionAssemblyInfo.cs | 6 +++--- appveyor.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SolutionAssemblyInfo.cs b/SolutionAssemblyInfo.cs index ccbfef5d0..afd76b5d7 100644 --- a/SolutionAssemblyInfo.cs +++ b/SolutionAssemblyInfo.cs @@ -16,6 +16,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("1.6.0")] -[assembly: AssemblyFileVersion("1.6.0")] -[assembly: AssemblyInformationalVersion("1.6.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.7.0")] +[assembly: AssemblyFileVersion("1.7.0")] +[assembly: AssemblyInformationalVersion("1.7.0")] \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 7d1da7f3f..2c2f43b66 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.6.0.{build}' +version: '1.7.0.{build}' init: - ps: | From a6609d6b2c86cc167ae15501494434294cb64dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 21:37:45 +0800 Subject: [PATCH 212/442] Add Cancellationtoken for downloadasync Remove extra action in http for cancellation due to it will throw TaskCancelledExcpetion internally --- Flow.Launcher.Infrastructure/Http/Http.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 4212a1ab2..5af50bc6d 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -76,11 +76,11 @@ namespace Flow.Launcher.Infrastructure.Http }; } - public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath) + public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default) { try { - using var response = await client.GetAsync(url); + using var response = await client.GetAsync(url, token); if (response.StatusCode == HttpStatusCode.OK) { await using var fileStream = new FileStream(filePath, FileMode.CreateNew); @@ -120,8 +120,6 @@ namespace Flow.Launcher.Infrastructure.Http { Log.Debug($"|Http.Get|Url <{url}>"); using var response = await client.GetAsync(url, token); - if (token.IsCancellationRequested) - return string.Empty; var content = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.OK) { @@ -143,8 +141,6 @@ namespace Flow.Launcher.Infrastructure.Http { Log.Debug($"|Http.Get|Url <{url}>"); var response = await client.GetAsync(url, token); - if (token.IsCancellationRequested) - return Stream.Null; return await response.Content.ReadAsStreamAsync(); } } From 8a5f98a6a3b4b73e302c39b2c77534fa32174151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 21:38:21 +0800 Subject: [PATCH 213/442] Manually handling TaskCancelledException in search suggestion to aviod stunt in debugging --- .../Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs | 4 ++++ .../Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs | 4 ++++ .../SuggestionSources/Google.cs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index 0c6d95b4f..dcc2b9eef 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -26,6 +26,10 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources const string api = "http://suggestion.baidu.com/su?json=1&wd="; result = await Http.GetAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); } + catch (TaskCanceledException) + { + return null; + } catch (HttpRequestException e) { Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index 991e45267..47bd016c1 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -24,6 +24,10 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources const string api = "https://api.bing.com/qsonhs.aspx?q="; resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); } + catch (TaskCanceledException) + { + return null; + } catch (HttpRequestException e) { Log.Exception("|Bing.Suggestions|Can't get suggestion from Bing", e); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index d034679af..1182013b6 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -22,6 +22,10 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources const string api = "https://www.google.com/complete/search?output=chrome&q="; result = await Http.GetAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); } + catch (TaskCanceledException) + { + return null; + } catch (HttpRequestException e) { Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); From 86a9cf31c5e67b61d9cb5fb74ba49e02f3b389f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 21:41:05 +0800 Subject: [PATCH 214/442] Optimize Websearch code --- .../Flow.Launcher.Plugin.WebSearch/Main.cs | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index a573674ec..f76e28112 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -40,65 +40,63 @@ namespace Flow.Launcher.Plugin.WebSearch var searchSourceList = new List(); var results = new List(); - _settings.SearchSources.Where(o => (o.ActionKeyword == query.ActionKeyword || o.ActionKeyword == SearchSourceGlobalPluginWildCardSign) - && o.Enabled) - .ToList() - .ForEach(x => searchSourceList.Add(x)); - - if (searchSourceList.Any()) + foreach (SearchSource searchSource in _settings.SearchSources.Where(o => (o.ActionKeyword == query.ActionKeyword || + o.ActionKeyword == SearchSourceGlobalPluginWildCardSign) + && o.Enabled)) { - foreach (SearchSource searchSource in searchSourceList) + string keyword = string.Empty; + keyword = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? query.ToString() : query.Search; + var title = keyword; + string subtitle = _context.API.GetTranslation("flowlauncher_plugin_websearch_search") + " " + searchSource.Title; + + if (string.IsNullOrEmpty(keyword)) { - string keyword = string.Empty; - keyword = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? query.ToString() : query.Search; - var title = keyword; - string subtitle = _context.API.GetTranslation("flowlauncher_plugin_websearch_search") + " " + searchSource.Title; - - if (string.IsNullOrEmpty(keyword)) + var result = new Result { - var result = new Result - { - Title = subtitle, - SubTitle = string.Empty, - IcoPath = searchSource.IconPath - }; - results.Add(result); - } - else - { - var result = new Result - { - Title = title, - SubTitle = subtitle, - Score = 6, - IcoPath = searchSource.IconPath, - ActionKeywordAssigned = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? string.Empty : searchSource.ActionKeyword, - Action = c => - { - if (_settings.OpenInNewBrowser) - { - searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)).NewBrowserWindow(_settings.BrowserPath); - } - else - { - searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)).NewTabInBrowser(_settings.BrowserPath); - } - - return true; - } - }; - - results.Add(result); - } - - ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs - { - Results = results, - Query = query - }); - - await UpdateResultsFromSuggestionAsync(results, keyword, subtitle, searchSource, query, token).ConfigureAwait(false); + Title = subtitle, + SubTitle = string.Empty, + IcoPath = searchSource.IconPath + }; + results.Add(result); } + else + { + var result = new Result + { + Title = title, + SubTitle = subtitle, + Score = 6, + IcoPath = searchSource.IconPath, + ActionKeywordAssigned = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? string.Empty : searchSource.ActionKeyword, + Action = c => + { + if (_settings.OpenInNewBrowser) + { + searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)).NewBrowserWindow(_settings.BrowserPath); + } + else + { + searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)).NewTabInBrowser(_settings.BrowserPath); + } + + return true; + } + }; + + results.Add(result); + } + + ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs + { + Results = results, + Query = query + }); + + await UpdateResultsFromSuggestionAsync(results, keyword, subtitle, searchSource, query, token).ConfigureAwait(false); + + if (token.IsCancellationRequested) + return null; + } return results; @@ -110,15 +108,13 @@ namespace Flow.Launcher.Plugin.WebSearch if (_settings.EnableSuggestion) { var suggestions = await SuggestionsAsync(keyword, subtitle, searchSource, token).ConfigureAwait(false); + if (token.IsCancellationRequested || !suggestions.Any()) + return; + + results.AddRange(suggestions); token.ThrowIfCancellationRequested(); - - ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs - { - Results = results, - Query = query - }); } } @@ -128,6 +124,10 @@ namespace Flow.Launcher.Plugin.WebSearch if (source != null) { var suggestions = await source.Suggestions(keyword, token); + + if (token.IsCancellationRequested) + return null; + var resultsFromSuggestion = suggestions.Select(o => new Result { Title = o, From 919d5d51ab5b8e3ffb15f10f74586e0d26681e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 21:47:31 +0800 Subject: [PATCH 215/442] add using for Bing search source --- .../Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index 47bd016c1..e6b0438d1 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -39,7 +39,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources JsonElement json; try { - json = (await JsonDocument.ParseAsync(resultStream)).RootElement.GetProperty("AS"); + using (resultStream) + json = (await JsonDocument.ParseAsync(resultStream)).RootElement.GetProperty("AS"); } catch (JsonException e) { From 1003ce416093d2320892c75f50b2082a522cc2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 21:48:55 +0800 Subject: [PATCH 216/442] move using to more specific place in Google.cs --- .../SuggestionSources/Google.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 17153b9a7..5c784a702 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -32,24 +32,24 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources return new List(); } - using (resultStream) + + if (resultStream.Length == 0) return new List(); + JsonDocument json; + try { - if (resultStream.Length == 0) return new List(); - JsonDocument json; - try - { + using (resultStream) json = await JsonDocument.ParseAsync(resultStream); - } - catch (JsonException e) - { - Log.Exception("|Google.Suggestions|can't parse suggestions", e); - return new List(); - } - - var results = json?.RootElement.EnumerateArray().ElementAt(1); - - return results?.EnumerateArray().Select(o => o.GetString()).ToList() ?? new List(); } + catch (JsonException e) + { + Log.Exception("|Google.Suggestions|can't parse suggestions", e); + return new List(); + } + + var results = json?.RootElement.EnumerateArray().ElementAt(1); + + return results?.EnumerateArray().Select(o => o.GetString()).ToList() ?? new List(); + } public override string ToString() From c939924ec8ec2e28e6eccf7422ac7bf9eb5f3e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 7 Jan 2021 21:54:09 +0800 Subject: [PATCH 217/442] optimize code in searchsuggestions --- .../SuggestionSources/Bing.cs | 16 +++++----------- .../SuggestionSources/Google.cs | 17 ++++++----------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index e6b0438d1..81725c3f2 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -17,12 +17,15 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { public override async Task> Suggestions(string query, CancellationToken token) { - Stream resultStream; + JsonElement json; try { const string api = "https://api.bing.com/qsonhs.aspx?q="; - resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); + using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); + if (resultStream.Length == 0) return new List(); // this handles the cancellation + json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token)).RootElement.GetProperty("AS"); + } catch (TaskCanceledException) { @@ -33,15 +36,6 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources Log.Exception("|Bing.Suggestions|Can't get suggestion from Bing", e); return new List(); } - - if (resultStream.Length == 0) return new List(); // this handles the cancellation - - JsonElement json; - try - { - using (resultStream) - json = (await JsonDocument.ParseAsync(resultStream)).RootElement.GetProperty("AS"); - } catch (JsonException e) { Log.Exception("|Bing.Suggestions|can't parse suggestions", e); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 5c784a702..b150d26c1 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -16,11 +16,15 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { public override async Task> Suggestions(string query, CancellationToken token) { - Stream resultStream; + JsonDocument json; + try { const string api = "https://www.google.com/complete/search?output=chrome&q="; - resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); + using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); + if (resultStream.Length == 0) return new List(); + json = await JsonDocument.ParseAsync(resultStream); + } catch (TaskCanceledException) { @@ -31,15 +35,6 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); return new List(); } - - - if (resultStream.Length == 0) return new List(); - JsonDocument json; - try - { - using (resultStream) - json = await JsonDocument.ParseAsync(resultStream); - } catch (JsonException e) { Log.Exception("|Google.Suggestions|can't parse suggestions", e); From a8e4c504d066a36498e7cbeb33f14797ac3e35f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 8 Jan 2021 15:52:45 +0800 Subject: [PATCH 218/442] Move MatchResult to Flow.Launcher.Plugin so that plugins can utilize main method --- Flow.Launcher.Infrastructure/StringMatcher.cs | 69 +---------------- .../UserSettings/Settings.cs | 11 +-- .../Flow.Launcher.Plugin.csproj | 2 +- Flow.Launcher.Plugin/IPublicAPI.cs | 5 +- .../SharedModel/MatchResult.cs | 74 ++++++++++++++++++ Flow.Launcher.Test/FuzzyMatcherTest.cs | 75 ++++++++++--------- Flow.Launcher/PublicAPIInstance.cs | 7 +- .../ViewModel/SettingWindowViewModel.cs | 3 +- .../Commands/Bookmarks.cs | 1 + 9 files changed, 129 insertions(+), 118 deletions(-) create mode 100644 Flow.Launcher.Plugin/SharedModel/MatchResult.cs diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 2a4270fb4..300404b9e 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -1,3 +1,4 @@ +using Flow.Launcher.Plugin.SharedModel; using System; using System.Collections.Generic; using System.ComponentModel; @@ -239,75 +240,9 @@ namespace Flow.Launcher.Infrastructure return score; } - - public enum SearchPrecisionScore - { - Regular = 50, - Low = 20, - None = 0 - } } - public class MatchResult - { - public MatchResult(bool success, SearchPrecisionScore searchPrecision) - { - Success = success; - SearchPrecision = searchPrecision; - } - - public MatchResult(bool success, SearchPrecisionScore searchPrecision, List matchData, int rawScore) - { - Success = success; - SearchPrecision = searchPrecision; - MatchData = matchData; - RawScore = rawScore; - } - - public bool Success { get; set; } - - /// - /// The final score of the match result with search precision filters applied. - /// - public int Score { get; private set; } - - /// - /// The raw calculated search score without any search precision filtering applied. - /// - private int _rawScore; - - public int RawScore - { - get { return _rawScore; } - set - { - _rawScore = value; - Score = ScoreAfterSearchPrecisionFilter(_rawScore); - } - } - - /// - /// Matched data to highlight. - /// - public List MatchData { get; set; } - - public SearchPrecisionScore SearchPrecision { get; set; } - - public bool IsSearchPrecisionScoreMet() - { - return IsSearchPrecisionScoreMet(_rawScore); - } - - private bool IsSearchPrecisionScoreMet(int rawScore) - { - return rawScore >= (int)SearchPrecision; - } - - private int ScoreAfterSearchPrecisionFilter(int rawScore) - { - return IsSearchPrecisionScoreMet(rawScore) ? rawScore : 0; - } - } + public class MatchOption { diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 837fe3b71..891ce7924 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -4,6 +4,7 @@ using System.Drawing; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModel; namespace Flow.Launcher.Infrastructure.UserSettings { @@ -30,7 +31,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings /// public bool ShouldUsePinyin { get; set; } = false; - internal StringMatcher.SearchPrecisionScore QuerySearchPrecision { get; private set; } = StringMatcher.SearchPrecisionScore.Regular; + internal SearchPrecisionScore QuerySearchPrecision { get; private set; } = SearchPrecisionScore.Regular; [JsonIgnore] public string QuerySearchPrecisionString @@ -40,8 +41,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings { try { - var precisionScore = (StringMatcher.SearchPrecisionScore)Enum - .Parse(typeof(StringMatcher.SearchPrecisionScore), value); + var precisionScore = (SearchPrecisionScore)Enum + .Parse(typeof(SearchPrecisionScore), value); QuerySearchPrecision = precisionScore; StringMatcher.Instance.UserSettingSearchPrecision = precisionScore; @@ -50,8 +51,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings { Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e); - QuerySearchPrecision = StringMatcher.SearchPrecisionScore.Regular; - StringMatcher.Instance.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular; + QuerySearchPrecision = SearchPrecisionScore.Regular; + StringMatcher.Instance.UserSettingSearchPrecision = SearchPrecisionScore.Regular; throw; } diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 0f6450d18..cefbfb1c8 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index b6f3f0680..ae554044e 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -1,4 +1,5 @@ -using System; +using Flow.Launcher.Plugin.SharedModel; +using System; using System.Collections.Generic; namespace Flow.Launcher.Plugin @@ -89,6 +90,6 @@ namespace Flow.Launcher.Plugin /// event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; - public (List MatchedData, int Score, bool Success) MatchString(string query, string stringToCompare); + public MatchResult FuzzySearch(string query, string stringToCompare); } } diff --git a/Flow.Launcher.Plugin/SharedModel/MatchResult.cs b/Flow.Launcher.Plugin/SharedModel/MatchResult.cs new file mode 100644 index 000000000..26fc09cb9 --- /dev/null +++ b/Flow.Launcher.Plugin/SharedModel/MatchResult.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Flow.Launcher.Plugin.SharedModel +{ + public class MatchResult + { + public MatchResult(bool success, SearchPrecisionScore searchPrecision) + { + Success = success; + SearchPrecision = searchPrecision; + } + + public MatchResult(bool success, SearchPrecisionScore searchPrecision, List matchData, int rawScore) + { + Success = success; + SearchPrecision = searchPrecision; + MatchData = matchData; + RawScore = rawScore; + } + + public bool Success { get; set; } + + /// + /// The final score of the match result with search precision filters applied. + /// + public int Score { get; private set; } + + /// + /// The raw calculated search score without any search precision filtering applied. + /// + private int _rawScore; + + public int RawScore + { + get { return _rawScore; } + set + { + _rawScore = value; + Score = ScoreAfterSearchPrecisionFilter(_rawScore); + } + } + + /// + /// Matched data to highlight. + /// + public List MatchData { get; set; } + + public SearchPrecisionScore SearchPrecision { get; set; } + + public bool IsSearchPrecisionScoreMet() + { + return IsSearchPrecisionScoreMet(_rawScore); + } + + private bool IsSearchPrecisionScoreMet(int rawScore) + { + return rawScore >= (int)SearchPrecision; + } + + private int ScoreAfterSearchPrecisionFilter(int rawScore) + { + return IsSearchPrecisionScoreMet(rawScore) ? rawScore : 0; + } + } + + public enum SearchPrecisionScore + { + Regular = 50, + Low = 20, + None = 0 + } +} diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 468b94457..97e5f2574 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModel; namespace Flow.Launcher.Test { @@ -37,8 +38,8 @@ namespace Flow.Launcher.Test { var listToReturn = new List(); - Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)) - .Cast() + Enum.GetValues(typeof(SearchPrecisionScore)) + .Cast() .ToList() .ForEach(x => listToReturn.Add((int)x)); @@ -145,20 +146,20 @@ namespace Flow.Launcher.Test $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}"); } - [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)] + [TestCase("goo", "Google Chrome", SearchPrecisionScore.Regular, true)] + [TestCase("chr", "Google Chrome", SearchPrecisionScore.Low, true)] + [TestCase("chr", "Chrome", SearchPrecisionScore.Regular, true)] + [TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)] + [TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Low, true)] + [TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.Regular, false)] + [TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.None, true)] + [TestCase("ccs", "Candy Crush Saga from King", SearchPrecisionScore.Low, true)] + [TestCase("cand", "Candy Crush Saga from King",SearchPrecisionScore.Regular, true)] + [TestCase("cand", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)] public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( string queryString, string compareString, - StringMatcher.SearchPrecisionScore expectedPrecisionScore, + SearchPrecisionScore expectedPrecisionScore, bool expectedPrecisionResult) { // When @@ -182,32 +183,32 @@ namespace Flow.Launcher.Test $"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("servez", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)] - [TestCase("sql servz", 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)] - [TestCase("cod", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] - [TestCase("code", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] - [TestCase("codes", "Visual Studio Codes", StringMatcher.SearchPrecisionScore.Regular, true)] + [TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", SearchPrecisionScore.Regular, false)] + [TestCase("term", "Windows Terminal (Preview)", SearchPrecisionScore.Regular, true)] + [TestCase("sql s managa", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)] + [TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)] + [TestCase("sql s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] + [TestCase("sql manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] + [TestCase("sql", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] + [TestCase("sql serv", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] + [TestCase("servez", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)] + [TestCase("sql servz", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)] + [TestCase("sql serv man", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] + [TestCase("sql studio", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] + [TestCase("mic", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] + [TestCase("chr", "Shutdown", SearchPrecisionScore.Regular, false)] + [TestCase("mssms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)] + [TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, false)] + [TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, true)] + [TestCase("a test", "This is a test", SearchPrecisionScore.Regular, true)] + [TestCase("test", "This is a test", SearchPrecisionScore.Regular, true)] + [TestCase("cod", VisualStudioCode, SearchPrecisionScore.Regular, true)] + [TestCase("code", VisualStudioCode, SearchPrecisionScore.Regular, true)] + [TestCase("codes", "Visual Studio Codes", SearchPrecisionScore.Regular, true)] public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings( string queryString, string compareString, - StringMatcher.SearchPrecisionScore expectedPrecisionScore, + SearchPrecisionScore expectedPrecisionScore, bool expectedPrecisionResult) { // When @@ -238,7 +239,7 @@ namespace Flow.Launcher.Test string queryString, string compareString1, string compareString2) { // When - var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular }; + var matcher = new StringMatcher { UserSettingSearchPrecision = SearchPrecisionScore.Regular }; // Given var compareString1Result = matcher.FuzzyMatch(queryString, compareString1); diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 5d1ea7f24..9cf3b0f4e 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -14,6 +14,7 @@ using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; +using Flow.Launcher.Plugin.SharedModel; namespace Flow.Launcher { @@ -132,11 +133,7 @@ namespace Flow.Launcher public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; - public (List MatchedData, int Score, bool Success) MatchString(string query, string stringToCompare) - { - var result = StringMatcher.FuzzySearch(query, stringToCompare); - return (result.MatchData, result.Score, result.Success); - } + public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); #endregion diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 853925852..5eec3962a 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -17,6 +17,7 @@ using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModel; namespace Flow.Launcher.ViewModel { @@ -152,7 +153,7 @@ namespace Flow.Launcher.ViewModel { var precisionStrings = new List(); - var enumList = Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)).Cast().ToList(); + var enumList = Enum.GetValues(typeof(SearchPrecisionScore)).Cast().ToList(); enumList.ForEach(x => precisionStrings.Add(x.ToString())); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/Bookmarks.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/Bookmarks.cs index c7013aa67..4e3f4f513 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/Bookmarks.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/Bookmarks.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Flow.Launcher.Infrastructure; +using Flow.Launcher.Plugin.SharedModel; namespace Flow.Launcher.Plugin.BrowserBookmark.Commands { From 4f35e621616baf5477e853d186c503e6a3cbc69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 8 Jan 2021 16:00:06 +0800 Subject: [PATCH 219/442] Add Http.Get and Http.GetAsync to IPublicAPI --- Flow.Launcher.Plugin/IPublicAPI.cs | 9 ++++++++- Flow.Launcher/PublicAPIInstance.cs | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index ae554044e..147741ba8 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -1,6 +1,9 @@ using Flow.Launcher.Plugin.SharedModel; using System; using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin { @@ -90,6 +93,10 @@ namespace Flow.Launcher.Plugin /// event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; - public MatchResult FuzzySearch(string query, string stringToCompare); + MatchResult FuzzySearch(string query, string stringToCompare); + + Task HttpGetStringAsync(string url, CancellationToken token = default); + + Task HttpGetStreamAsync(string url, CancellationToken token = default); } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 9cf3b0f4e..6f6a4a011 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -15,6 +15,8 @@ using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; using Flow.Launcher.Plugin.SharedModel; +using System.Threading; +using System.IO; namespace Flow.Launcher { @@ -135,6 +137,16 @@ namespace Flow.Launcher public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); + public Task HttpGetStringAsync(string url, CancellationToken token = default) + { + return null; + } + + public Task HttpGetStreamAsync(string url, CancellationToken token = default) + { + return null; + } + #endregion #region Private Methods From a3975a353167a2e63426be2d11abb74dd088f05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 8 Jan 2021 16:01:39 +0800 Subject: [PATCH 220/442] implement the Http method in publicapiinstance --- Flow.Launcher/PublicAPIInstance.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index e0b9c153f..e214d73e7 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -17,6 +17,7 @@ using Flow.Launcher.ViewModel; using Flow.Launcher.Plugin.SharedModel; using System.Threading; using System.IO; +using Flow.Launcher.Infrastructure.Http; namespace Flow.Launcher { @@ -134,12 +135,12 @@ namespace Flow.Launcher public Task HttpGetStringAsync(string url, CancellationToken token = default) { - return null; + return Http.GetAsync(url); } public Task HttpGetStreamAsync(string url, CancellationToken token = default) { - return null; + return Http.GetStreamAsync(url); } #endregion From bd74a87d0880891473b5df617a3c7e92dc75451e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 8 Jan 2021 16:05:50 +0800 Subject: [PATCH 221/442] add Http.DownloadAsync --- Flow.Launcher.Plugin/IPublicAPI.cs | 3 +++ Flow.Launcher/PublicAPIInstance.cs | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index 9083b2d74..4c89973ae 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Plugin.SharedModel; +using JetBrains.Annotations; using System; using System.Collections.Generic; using System.IO; @@ -92,5 +93,7 @@ namespace Flow.Launcher.Plugin Task HttpGetStringAsync(string url, CancellationToken token = default); Task HttpGetStreamAsync(string url, CancellationToken token = default); + + Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath); } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index e214d73e7..809a4b920 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -18,6 +18,7 @@ using Flow.Launcher.Plugin.SharedModel; using System.Threading; using System.IO; using Flow.Launcher.Infrastructure.Http; +using JetBrains.Annotations; namespace Flow.Launcher { @@ -96,7 +97,7 @@ namespace Flow.Launcher { Application.Current.Dispatcher.Invoke(() => { - var msg = useMainWindowAsOwner ? new Msg {Owner = Application.Current.MainWindow} : new Msg(); + var msg = useMainWindowAsOwner ? new Msg { Owner = Application.Current.MainWindow } : new Msg(); msg.Show(title, subTitle, iconPath); }); } @@ -143,6 +144,11 @@ namespace Flow.Launcher return Http.GetStreamAsync(url); } + public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath) + { + return Http.DownloadAsync(url, filePath); + } + #endregion #region Private Methods From 4f5b2d35e8eb69aae37741a22e725b00d00e5135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 8 Jan 2021 16:08:39 +0800 Subject: [PATCH 222/442] Allow plugin to add & remove actionkeywords --- Flow.Launcher.Plugin/IPublicAPI.cs | 5 +++++ Flow.Launcher/PublicAPIInstance.cs | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index 4c89973ae..b28abbfcc 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -95,5 +95,10 @@ namespace Flow.Launcher.Plugin Task HttpGetStreamAsync(string url, CancellationToken token = default); Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath); + + void AddActionKeyword(string pluginId, string newActionKeyword); + + void RemoveActionKeyword(string pluginId, string oldActionKeyword); + } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 809a4b920..5c892c557 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -149,6 +149,15 @@ namespace Flow.Launcher return Http.DownloadAsync(url, filePath); } + public void AddActionKeyword(string pluginId, string newActionKeyword) + { + PluginManager.AddActionKeyword(pluginId, newActionKeyword); + } + + public void RemoveActionKeyword(string pluginId, string oldActionKeyword) + { + PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); + } #endregion #region Private Methods From 6d5ade405694903859546b5392d5e81f349159e6 Mon Sep 17 00:00:00 2001 From: kubalav Date: Fri, 8 Jan 2021 09:44:26 +0100 Subject: [PATCH 223/442] Plugins Manager Slovak translation --- .../Languages/sk.xaml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml new file mode 100644 index 000000000..21ec558a6 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml @@ -0,0 +1,39 @@ + + + + Sťahovanie pluginu + Čakajte, prosím… + Úspešne stiahnuté + {0} od {1} {2}{3}Chcete odinštalovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje. + {0} by {1} {2}{3}Chcete nainštalovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje. + Inštalovať plugin + Odinštalovať plugin + Inštalácia zlyhala: nepodarilo sa nájsť metadáta súboru plugin.json nového pluginu + Chyba inštalácie pluginu + Nastala chyba počas inštaláciu pluginu {0} + Nie je k dispozícii žiadna aktualizácia + Všetky pluginy sú aktuálne + {0} od {1} {2}{3}Chcete aktualizovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje. + Aktualizácia pluginu + Tento plugin má dostupnú aktualizáciu, chcete ju zobraziť? + Tento plugin je už nainštalovaný + + + + + Správca pluginov + Správa inštalácie, odinštalácie alebo aktualizácie pluginov programu Flow Launcher + + + Prejsť na webovú stránku + Prejsť na webovú stránku pluginu + Zobraziť zdrojový kód + Zobraziť zdrojový kód pluginu + Navrhnúť vylepšenie alebo nahlásiť chybu + Navrhnúť vylepšenie alebo nahlásiť chybu vývojárovi pluginu + Prejsť na repozitár pluginov spúšťača Flow + Prejsť na repozitár pluginov spúšťača Flow a zobraziť príspevky komunity + + \ No newline at end of file From 49560db7f64d7535ffc7dfba41a8470a09ed8257 Mon Sep 17 00:00:00 2001 From: Ioannis G Date: Sat, 9 Jan 2021 02:49:14 +0200 Subject: [PATCH 224/442] copy svg images to output from the main project --- Flow.Launcher/Flow.Launcher.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index c3b56b904..fddaaab6a 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -63,6 +63,9 @@ PreserveNewest + + PreserveNewest + From 494312a251b03d8870e937ab3bc03f0d0936b342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 9 Jan 2021 11:04:36 +0800 Subject: [PATCH 225/442] Merge dev --- .../Flow.Launcher.Plugin.PluginsManager.csproj | 2 +- .../Flow.Launcher.Plugin.Shell.csproj | 5 +---- .../Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj | 5 +---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index 2e352d832..d6ca96b46 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -2,7 +2,7 @@ Library - netcoreapp3.1 + net5.0-windows true true true diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index d19c41cb2..daae28cf6 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -7,6 +7,7 @@ Flow.Launcher.Plugin.Shell Flow.Launcher.Plugin.Shell true + true true false false @@ -53,10 +54,6 @@ PreserveNewest - - MSBuild:Compile - Designer - diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index 1bc5230e7..894d50cea 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -8,6 +8,7 @@ Flow.Launcher.Plugin.Sys Flow.Launcher.Plugin.Sys true + true true false false @@ -48,10 +49,6 @@ PreserveNewest - - MSBuild:Compile - Designer - From dd074f41cfd07138b2e14907198c0178df97ca95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 9 Jan 2021 11:30:11 +0800 Subject: [PATCH 226/442] change publish profile to Net5 one --- Scripts/post_build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/post_build.ps1 b/Scripts/post_build.ps1 index b08fac8f6..58d5c6a4e 100644 --- a/Scripts/post_build.ps1 +++ b/Scripts/post_build.ps1 @@ -90,7 +90,7 @@ function Pack-Squirrel-Installer ($path, $version, $output) { function Publish-Self-Contained ($p) { $csproj = Join-Path "$p" "Flow.Launcher/Flow.Launcher.csproj" -Resolve - $profile = Join-Path "$p" "Flow.Launcher/Properties/PublishProfiles/NetCore3.1-SelfContained.pubxml" -Resolve + $profile = Join-Path "$p" "Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml" -Resolve # we call dotnet publish on the main project. # The other projects should have been built in Release at this point. From a626e7b6ee8133d2f06cc01ad2467d49013d0b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 9 Jan 2021 11:30:19 +0800 Subject: [PATCH 227/442] try trim --- .../Properties/PublishProfiles/Net5-SelfContained.pubxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml b/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml index 124792e3e..f2f969aa7 100644 --- a/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml +++ b/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml @@ -13,6 +13,6 @@ https://go.microsoft.com/fwlink/?LinkID=208121. true False False - False + True \ No newline at end of file From 92be6fd3ddd1024e320aa6d0b98ab1000fe7e5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 9 Jan 2021 12:01:08 +0800 Subject: [PATCH 228/442] Use Task.Yield to avoid using Parallel.For --- Flow.Launcher/ViewModel/MainViewModel.cs | 59 +++++++++++------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index da977f11f..c3e32731b 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -86,7 +86,7 @@ namespace Flow.Launcher.ViewModel { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated)pair.Plugin; + var plugin = (IResultUpdated) pair.Plugin; plugin.ResultsUpdated += (s, e) => { Task.Run(() => @@ -113,25 +113,13 @@ namespace Flow.Launcher.ViewModel } }); - SelectNextItemCommand = new RelayCommand(_ => - { - SelectedResults.SelectNextResult(); - }); + SelectNextItemCommand = new RelayCommand(_ => { SelectedResults.SelectNextResult(); }); - SelectPrevItemCommand = new RelayCommand(_ => - { - SelectedResults.SelectPrevResult(); - }); + SelectPrevItemCommand = new RelayCommand(_ => { SelectedResults.SelectPrevResult(); }); - SelectNextPageCommand = new RelayCommand(_ => - { - SelectedResults.SelectNextPage(); - }); + SelectNextPageCommand = new RelayCommand(_ => { SelectedResults.SelectNextPage(); }); - SelectPrevPageCommand = new RelayCommand(_ => - { - SelectedResults.SelectPrevPage(); - }); + SelectPrevPageCommand = new RelayCommand(_ => { SelectedResults.SelectPrevPage(); }); SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult()); @@ -209,6 +197,7 @@ namespace Flow.Launcher.ViewModel public ResultsViewModel History { get; private set; } private string _queryText; + public string QueryText { get { return _queryText; } @@ -229,10 +218,12 @@ namespace Flow.Launcher.ViewModel QueryTextCursorMovedToEnd = true; QueryText = queryText; } + public bool LastQuerySelected { get; set; } public bool QueryTextCursorMovedToEnd { get; set; } private ResultsViewModel _selectedResults; + private ResultsViewModel SelectedResults { get { return _selectedResults; } @@ -264,6 +255,7 @@ namespace Flow.Launcher.ViewModel QueryText = string.Empty; } } + _selectedResults.Visbility = Visibility.Visible; } } @@ -324,7 +316,7 @@ namespace Flow.Launcher.ViewModel var filtered = results.Where ( r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() - || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() + || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() ).ToList(); ContextMenu.AddResults(filtered, id); } @@ -351,7 +343,7 @@ namespace Flow.Launcher.ViewModel Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query { RawQuery = h.Query }, + OriginQuery = new Query {RawQuery = h.Query}, Action = _ => { SelectedResults = Results; @@ -397,7 +389,8 @@ namespace Flow.Launcher.ViewModel _lastQuery = query; 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 + { + // 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; @@ -410,17 +403,14 @@ namespace Flow.Launcher.ViewModel // so looping will stop once it was cancelled Task[] tasks = new Task[plugins.Count]; - var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; try { - Parallel.For(0, plugins.Count, parallelOptions, i => + for (var i = 0; i < plugins.Count; i++) { if (!plugins[i].Metadata.Disabled) - { tasks[i] = QueryTask(plugins[i], query, currentCancellationToken); - } else tasks[i] = Task.CompletedTask; // Avoid Null - }); + } // Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first await Task.WhenAll(tasks); @@ -434,20 +424,25 @@ namespace Flow.Launcher.ViewModel // until the end of all querying _isQueryRunning = false; if (!currentCancellationToken.IsCancellationRequested) - { // update to hidden if this is still the current query + { + // update to hidden if this is still the current query ProgressBarVisibility = Visibility.Hidden; } // Local Function async Task QueryTask(PluginPair plugin, Query query, CancellationToken token) { + // Since it is wrapped within a Task.Run, the synchronous context is null + // Task.Yield will force it to run in ThreadPool + await Task.Yield(); + var results = await PluginManager.QueryForPlugin(plugin, query, token); if (!currentCancellationToken.IsCancellationRequested) UpdateResultView(results, plugin.Metadata, query); } - - }, currentCancellationToken).ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), - TaskContinuationOptions.OnlyOnFaulted); + }, currentCancellationToken).ContinueWith( + t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), + TaskContinuationOptions.OnlyOnFaulted); } } else @@ -514,6 +509,7 @@ namespace Flow.Launcher.ViewModel } }; } + return menu; } @@ -559,6 +555,7 @@ namespace Flow.Launcher.ViewModel var selected = SelectedResults == History; return selected; } + #region Hotkey private void SetHotkey(string hotkeyStr, EventHandler action) @@ -577,7 +574,8 @@ namespace Flow.Launcher.ViewModel catch (Exception) { string errorMsg = - string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); + string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), + hotkeyStr); MessageBox.Show(errorMsg); } } @@ -627,7 +625,6 @@ namespace Flow.Launcher.ViewModel { if (!ShouldIgnoreHotkeys()) { - if (_settings.LastQueryMode == LastQueryMode.Empty) { ChangeQueryText(string.Empty); From 0fc9f64e8584a44e7d88f8d6579a7759bf3f44fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 9 Jan 2021 12:05:55 +0800 Subject: [PATCH 229/442] image not found issue in build --- Flow.Launcher/Flow.Launcher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index a269425af..a4d2634e0 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -60,7 +60,7 @@ Designer PreserveNewest - + PreserveNewest From 16148d59d4577d3db2efefdd3d9ccb9b6dbd25f7 Mon Sep 17 00:00:00 2001 From: kubalav Date: Sat, 9 Jan 2021 13:24:18 +0100 Subject: [PATCH 230/442] Fix Plugins Manager key name in Slovak translation --- Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml index 21ec558a6..211f2b430 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml @@ -27,7 +27,7 @@ Správa inštalácie, odinštalácie alebo aktualizácie pluginov programu Flow Launcher - Prejsť na webovú stránku + Prejsť na webovú stránku Prejsť na webovú stránku pluginu Zobraziť zdrojový kód Zobraziť zdrojový kód pluginu From 15a5bbabb8f8dbbcbde05c745c32c50ae34289c1 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 10 Jan 2021 13:17:40 +1100 Subject: [PATCH 231/442] version bump for PluginsManager --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index ef2c1255a..60ab7ab18 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.4.1", + "Version": "1.5.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From 97052b1fbf3bb826a2dac4c4dcee92879161d32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 10 Jan 2021 11:13:46 +0800 Subject: [PATCH 232/442] fix merge issue --- Flow.Launcher/ViewModel/MainViewModel.cs | 160 ++++++++++++----------- 1 file changed, 83 insertions(+), 77 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 3751d8c61..ac63f0cc3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -54,7 +54,6 @@ namespace Flow.Launcher.ViewModel private BufferBlock _resultsUpdateQueue; private Task _resultsViewUpdateTask; - #endregion #region Constructor @@ -95,7 +94,7 @@ namespace Flow.Launcher.ViewModel { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated)pair.Plugin; + var plugin = (IResultUpdated) pair.Plugin; plugin.ResultsUpdated += (s, e) => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); @@ -108,7 +107,8 @@ namespace Flow.Launcher.ViewModel private void RegisterViewUpdate() { _resultsUpdateQueue = new BufferBlock(); - _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); + _resultsViewUpdateTask = + Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); async Task updateAction() @@ -119,7 +119,9 @@ namespace Flow.Launcher.ViewModel _resultsUpdateQueue.TryReceiveAll(out var queue); UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); } - }; + } + + ; void continueAction(Task t) { @@ -127,7 +129,8 @@ namespace Flow.Launcher.ViewModel throw t.Exception; #else Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}"); - _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); + _resultsViewUpdateTask = + Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); #endif } } @@ -147,25 +150,13 @@ namespace Flow.Launcher.ViewModel } }); - SelectNextItemCommand = new RelayCommand(_ => - { - SelectedResults.SelectNextResult(); - }); + SelectNextItemCommand = new RelayCommand(_ => { SelectedResults.SelectNextResult(); }); - SelectPrevItemCommand = new RelayCommand(_ => - { - SelectedResults.SelectPrevResult(); - }); + SelectPrevItemCommand = new RelayCommand(_ => { SelectedResults.SelectPrevResult(); }); - SelectNextPageCommand = new RelayCommand(_ => - { - SelectedResults.SelectNextPage(); - }); + SelectNextPageCommand = new RelayCommand(_ => { SelectedResults.SelectNextPage(); }); - SelectPrevPageCommand = new RelayCommand(_ => - { - SelectedResults.SelectPrevPage(); - }); + SelectPrevPageCommand = new RelayCommand(_ => { SelectedResults.SelectPrevPage(); }); SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult()); @@ -244,6 +235,7 @@ namespace Flow.Launcher.ViewModel private string _lastQueryText; private string _queryText; + public string QueryText { get { return _queryText; } @@ -264,10 +256,12 @@ namespace Flow.Launcher.ViewModel QueryTextCursorMovedToEnd = true; QueryText = queryText; } + public bool LastQuerySelected { get; set; } public bool QueryTextCursorMovedToEnd { get; set; } private ResultsViewModel _selectedResults; + private ResultsViewModel SelectedResults { get { return _selectedResults; } @@ -299,6 +293,7 @@ namespace Flow.Launcher.ViewModel QueryText = string.Empty; } } + _selectedResults.Visbility = Visibility.Visible; } } @@ -359,7 +354,7 @@ namespace Flow.Launcher.ViewModel var filtered = results.Where ( r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() - || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() + || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() ).ToList(); ContextMenu.AddResults(filtered, id); } @@ -386,7 +381,7 @@ namespace Flow.Launcher.ViewModel Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query { RawQuery = h.Query }, + OriginQuery = new Query {RawQuery = h.Query}, Action = _ => { SelectedResults = Results; @@ -436,64 +431,73 @@ namespace Flow.Launcher.ViewModel var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => - { - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) + { + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) + return; + } + + _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + { + ProgressBarVisibility = Visibility.Visible; + } + }, currentCancellationToken); + + // so looping will stop once it was cancelled + var parallelOptions = new ParallelOptions {CancellationToken = currentCancellationToken}; + try + { + Parallel.ForEach(plugins, parallelOptions, plugin => + { + if (!plugin.Metadata.Disabled) + { + try + { + var results = PluginManager.QueryForPlugin(plugin, query); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, + query, + currentCancellationToken)); + } + catch (Exception e) + { + Log.Exception("MainViewModel", + $"Exception when querying the plugin {plugin.Metadata.Name}", e, + "QueryResults"); + } + } + }); + } + catch (OperationCanceledException) + { + // nothing to do here + } + if (currentCancellationToken.IsCancellationRequested) return; - } - _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + // this should happen once after all queries are done so progress bar should continue + // until the end of all querying + _isQueryRunning = false; + if (!currentCancellationToken.IsCancellationRequested) { - ProgressBarVisibility = Visibility.Visible; + // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; } - }, currentCancellationToken); - - // so looping will stop once it was cancelled - var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; - try - { - Parallel.ForEach(plugins, parallelOptions, plugin => + }, currentCancellationToken) + .ContinueWith( + t => { - if (!plugin.Metadata.Disabled) - { - try - { - var results = PluginManager.QueryForPlugin(plugin, query); - UpdateResultView(results, plugin.Metadata, query); - } - catch (Exception e) - { - Log.Exception("MainViewModel", $"Exception when querying the plugin {plugin.Metadata.Name}", e, "QueryResults"); - } - } - }); - } - catch (OperationCanceledException) - { - // nothing to do here - } - - if (currentCancellationToken.IsCancellationRequested) - return; - - // this should happen once after all queries are done so progress bar should continue - // until the end of all querying - _isQueryRunning = false; - if (!currentCancellationToken.IsCancellationRequested) - { // update to hidden if this is still the current query - ProgressBarVisibility = Visibility.Hidden; - } - }, currentCancellationToken).ContinueWith(t => - { - Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults"); - }, TaskContinuationOptions.OnlyOnFaulted); + Log.Exception("MainViewModel", "Error when querying plugins", + t.Exception?.InnerException, + "QueryResults"); + }, TaskContinuationOptions.OnlyOnFaulted); } } else @@ -561,6 +565,7 @@ namespace Flow.Launcher.ViewModel } }; } + return menu; } @@ -606,6 +611,7 @@ namespace Flow.Launcher.ViewModel var selected = SelectedResults == History; return selected; } + #region Hotkey private void SetHotkey(string hotkeyStr, EventHandler action) @@ -624,7 +630,8 @@ namespace Flow.Launcher.ViewModel catch (Exception) { string errorMsg = - string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); + string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), + hotkeyStr); MessageBox.Show(errorMsg); } } @@ -674,7 +681,6 @@ namespace Flow.Launcher.ViewModel { if (!ShouldIgnoreHotkeys()) { - if (_settings.LastQueryMode == LastQueryMode.Empty) { ChangeQueryText(string.Empty); @@ -724,6 +730,7 @@ namespace Flow.Launcher.ViewModel _saved = true; } } + /// /// To avoid deadlock, this method should not called from main thread /// @@ -787,7 +794,6 @@ namespace Flow.Launcher.ViewModel { Results.AddResults(list, metadata.ID); } - } #endregion From 691c2b06f9f3e156d851cecef3d351237a794234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 10 Jan 2021 11:14:00 +0800 Subject: [PATCH 233/442] increase edittime boundary --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index fc77327cc..932d6fc4b 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -336,7 +336,7 @@ namespace Flow.Launcher.ViewModel if (Count == 0 && newItems.Count == 0 || _token.IsCancellationRequested) return; - if (editTime < 5 || newItems.Count < 30) + if (editTime < 10 || newItems.Count < 30) { if (Count != 0) ClearItems(); AddRange(newItems); From db0fcb21713f92c18263ca829c9250a2fba9df00 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 10 Jan 2021 20:59:49 +1100 Subject: [PATCH 234/442] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 654877f3a..1056ec48e 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be - Support of wide range of plugins. - Fully portable. +[![image](https://user-images.githubusercontent.com/26427004/104119722-9033c600-5385-11eb-9d57-4c376862fd36.png) **SOFTPEDIA EDITOR'S PICK**](https://www.softpedia.com/get/System/Launchers-Shutdown-Tools/Flow-Launcher.shtml) + ## Running Flow Launcher | [Windows 7 and up](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest) | From b97834d9bcfbe3e11bfdad97be38c4ac98f34778 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 12 Jan 2021 07:30:01 +1100 Subject: [PATCH 235/442] update message + window location --- Flow.Launcher.Plugin/PluginMetadata.cs | 1 - Flow.Launcher/Languages/en.xaml | 2 +- Flow.Launcher/PriorityChangeWindow.xaml | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index d470b5556..e8f5cf744 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -45,7 +45,6 @@ namespace Flow.Launcher.Plugin [JsonIgnore] public int Priority { get; set; } - /// /// Init time include both plugin load time and init time /// diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 234fe5091..1e0e6a7e0 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -107,7 +107,7 @@ Release Notes - Higher the number, the closer the result will be to the top. If you want the results to be lower than any other plugin's, provide a negative number + Greater the number, the higher the result will be ranked. Try setting it as 5. If you want the results to be lower than any other plugin's, provide a negative number Please provide an valid integer for Priority! diff --git a/Flow.Launcher/PriorityChangeWindow.xaml b/Flow.Launcher/PriorityChangeWindow.xaml index 54e5c02cc..68b5a49b7 100644 --- a/Flow.Launcher/PriorityChangeWindow.xaml +++ b/Flow.Launcher/PriorityChangeWindow.xaml @@ -6,12 +6,13 @@ xmlns:local="clr-namespace:Flow.Launcher" Loaded="PriorityChangeWindow_Loaded" mc:Ignorable="d" + WindowStartupLocation="CenterScreen" Title="PriorityChangeWindow" Height="250" Width="300"> - + From b1642cc079c5a6490d305c349b0493d71457b78b Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 12 Jan 2021 07:31:13 +1100 Subject: [PATCH 236/442] priority score variable for clarity and seperation from original score --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index af472cf06..40006ecbf 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -687,7 +687,8 @@ namespace Flow.Launcher.ViewModel } else { - result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + metadata.Priority * 50; + var priorityScore = metadata.Priority * 50 + result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; } } From b9158e55664a9bfd199fad9d42d26cca12ece1a9 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 12 Jan 2021 07:36:23 +1100 Subject: [PATCH 237/442] update typo --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 40006ecbf..9b274d6e7 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -687,7 +687,7 @@ namespace Flow.Launcher.ViewModel } else { - var priorityScore = metadata.Priority * 50 + var priorityScore = metadata.Priority * 50; result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; } } From 94a047786e3cc59506b43038abe6a1a27fd2d5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 13 Jan 2021 07:35:48 +0800 Subject: [PATCH 238/442] fix merge issue of result update event --- Flow.Launcher/ViewModel/MainViewModel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 68c427672..9dea039a1 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -122,14 +122,14 @@ namespace Flow.Launcher.ViewModel { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated) pair.Plugin; + var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { - Task.Run(() => + if (e.Query.RawQuery == QueryText) // TODO: allow cancellation { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - UpdateResultView(e.Results, pair.Metadata, e.Query); - }, _updateToken); + _resultsUpdateQueue.Post(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); + } }; } } @@ -379,7 +379,7 @@ namespace Flow.Launcher.ViewModel Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query {RawQuery = h.Query}, + OriginQuery = new Query { RawQuery = h.Query }, Action = _ => { SelectedResults = Results; From 3892b59724d7db36adb2725b05abbc2ccfd12677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 13 Jan 2021 09:25:46 +0800 Subject: [PATCH 239/442] Remove extra null check (use string.iswhitespaceornull) --- Flow.Launcher/ViewModel/MainViewModel.cs | 187 +++++++++++------------ 1 file changed, 92 insertions(+), 95 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ac63f0cc3..0152cd854 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -94,7 +94,7 @@ namespace Flow.Launcher.ViewModel { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated) pair.Plugin; + var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); @@ -381,7 +381,7 @@ namespace Flow.Launcher.ViewModel Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query {RawQuery = h.Query}, + OriginQuery = new Query { RawQuery = h.Query }, Action = _ => { SelectedResults = Results; @@ -409,105 +409,102 @@ namespace Flow.Launcher.ViewModel private void QueryResults() { - if (!string.IsNullOrEmpty(QueryText)) + _updateSource?.Cancel(); + + if (string.IsNullOrWhiteSpace(QueryText)) { - _updateSource?.Cancel(); - _updateSource?.Dispose(); - - var currentUpdateSource = new CancellationTokenSource(); - _updateSource = currentUpdateSource; - var currentCancellationToken = _updateSource.Token; - _updateToken = currentCancellationToken; - - ProgressBarVisibility = Visibility.Hidden; - _isQueryRunning = true; - var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); - if (query != null) - { - // handle the exclusiveness of plugin using action keyword - RemoveOldQueryResults(query); - - _lastQuery = query; - - var plugins = PluginManager.ValidPluginsForQuery(query); - Task.Run(async () => - { - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) - { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested) - return; - } - - _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) - { - ProgressBarVisibility = Visibility.Visible; - } - }, currentCancellationToken); - - // so looping will stop once it was cancelled - var parallelOptions = new ParallelOptions {CancellationToken = currentCancellationToken}; - try - { - Parallel.ForEach(plugins, parallelOptions, plugin => - { - if (!plugin.Metadata.Disabled) - { - try - { - var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, - query, - currentCancellationToken)); - } - catch (Exception e) - { - Log.Exception("MainViewModel", - $"Exception when querying the plugin {plugin.Metadata.Name}", e, - "QueryResults"); - } - } - }); - } - catch (OperationCanceledException) - { - // nothing to do here - } - - if (currentCancellationToken.IsCancellationRequested) - return; - - // this should happen once after all queries are done so progress bar should continue - // until the end of all querying - _isQueryRunning = false; - if (!currentCancellationToken.IsCancellationRequested) - { - // update to hidden if this is still the current query - ProgressBarVisibility = Visibility.Hidden; - } - }, currentCancellationToken) - .ContinueWith( - t => - { - Log.Exception("MainViewModel", "Error when querying plugins", - t.Exception?.InnerException, - "QueryResults"); - }, TaskContinuationOptions.OnlyOnFaulted); - } - } - else - { - _updateSource?.Cancel(); Results.Clear(); Results.Visbility = Visibility.Collapsed; + return; } + + _updateSource?.Dispose(); + + var currentUpdateSource = new CancellationTokenSource(); + _updateSource = currentUpdateSource; + var currentCancellationToken = _updateSource.Token; + _updateToken = currentCancellationToken; + + ProgressBarVisibility = Visibility.Hidden; + _isQueryRunning = true; + + var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); + + // handle the exclusiveness of plugin using action keyword + RemoveOldQueryResults(query); + + _lastQuery = query; + + var plugins = PluginManager.ValidPluginsForQuery(query); + + Task.Run(async () => + { + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) + { + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) + return; + } + + _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + { + ProgressBarVisibility = Visibility.Visible; + } + }, currentCancellationToken); + + // so looping will stop once it was cancelled + var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; + try + { + Parallel.ForEach(plugins, parallelOptions, plugin => + { + if (!plugin.Metadata.Disabled) + { + try + { + var results = PluginManager.QueryForPlugin(plugin, query); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, + query, + currentCancellationToken)); + } + catch (Exception e) + { + Log.Exception("MainViewModel", + $"Exception when querying the plugin {plugin.Metadata.Name}", e, + "QueryResults"); + } + } + }); + } + catch (OperationCanceledException) + { + return; + // nothing to do here + } + + if (currentCancellationToken.IsCancellationRequested) + return; + + // this should happen once after all queries are done so progress bar should continue + // until the end of all querying + _isQueryRunning = false; + if (!currentCancellationToken.IsCancellationRequested) + { + // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; + } + }, currentCancellationToken) + .ContinueWith(t => Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults") + , TaskContinuationOptions.OnlyOnFaulted); + } + private void RemoveOldQueryResults(Query query) { string lastKeyword = _lastQuery.ActionKeyword; From fc536aa1ed1760c409b2974c3849ccfba62e2bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 13 Jan 2021 09:39:08 +0800 Subject: [PATCH 240/442] make priority score higher --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 9b274d6e7..e83e28c33 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -687,7 +687,7 @@ namespace Flow.Launcher.ViewModel } else { - var priorityScore = metadata.Priority * 50; + var priorityScore = metadata.Priority * 150; result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; } } From c39dfab6e55569a5538b1e37401a26bfc9bd7008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 13 Jan 2021 09:43:15 +0800 Subject: [PATCH 241/442] fix priority scoring --- Flow.Launcher/ViewModel/MainViewModel.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 0d79a55ec..cdac8f90d 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -755,15 +755,19 @@ namespace Flow.Launcher.ViewModel #endif - foreach (var result in resultsForUpdates.SelectMany(u => u.Results)) + foreach (var metaResults in resultsForUpdates) { - if (_topMostRecord.IsTopMost(result)) + foreach (var result in metaResults.Results) { - result.Score = int.MaxValue; - } - else - { - result.Score += _userSelectedRecord.GetSelectedCount(result) * 5; + if (_topMostRecord.IsTopMost(result)) + { + result.Score = int.MaxValue; + } + else + { + var priorityScore = metaResults.Metadata.Priority * 50; + result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; + } } } From ca7a8c2ef347dbf9b9a2006d049e81430e1a8d29 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 13 Jan 2021 19:39:54 +1100 Subject: [PATCH 242/442] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1056ec48e..ac8611298 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be - Support of wide range of plugins. - Fully portable. -[![image](https://user-images.githubusercontent.com/26427004/104119722-9033c600-5385-11eb-9d57-4c376862fd36.png) **SOFTPEDIA EDITOR'S PICK**](https://www.softpedia.com/get/System/Launchers-Shutdown-Tools/Flow-Launcher.shtml) +[ **SOFTPEDIA EDITOR'S PICK**](https://www.softpedia.com/get/System/Launchers-Shutdown-Tools/Flow-Launcher.shtml) ## Running Flow Launcher From a4c11df204663c33bb650e70452eff29bb18b9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 14 Jan 2021 10:24:44 +0800 Subject: [PATCH 243/442] use ready to run --- .../Properties/PublishProfiles/Net5-SelfContained.pubxml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml b/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml index f2f969aa7..3c5dcc8b2 100644 --- a/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml +++ b/Flow.Launcher/Properties/PublishProfiles/Net5-SelfContained.pubxml @@ -12,7 +12,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. win-x64 true False - False - True + True + False \ No newline at end of file From 763b51858fc094eb158b98ef023f9ccb24d8d44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 14 Jan 2021 12:24:41 +0800 Subject: [PATCH 244/442] Add comment in IPublic, IAsyncPlugin, IReloadable, IAsyncReloadable --- Flow.Launcher.Plugin/IAsyncPlugin.cs | 15 +++++++++++++ Flow.Launcher.Plugin/IPlugin.cs | 22 ++++++++++++++++++- .../Interfaces/IAsyncReloadable.cs | 11 ++++++++++ .../Interfaces/IReloadable.cs | 6 ++++- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/IAsyncPlugin.cs b/Flow.Launcher.Plugin/IAsyncPlugin.cs index 36f098e7d..3e1a086bc 100644 --- a/Flow.Launcher.Plugin/IAsyncPlugin.cs +++ b/Flow.Launcher.Plugin/IAsyncPlugin.cs @@ -4,9 +4,24 @@ using System.Threading.Tasks; namespace Flow.Launcher.Plugin { + /// + /// Asynchronous Plugin Model for Flow Launcher + /// public interface IAsyncPlugin { + /// + /// Asynchronous Querying + /// + /// Query to search + /// Cancel when querying job is obsolete + /// Task> QueryAsync(Query query, CancellationToken token); + + /// + /// Initialize plugin asynchrously (will still wait finish to continue) + /// + /// + /// Task InitAsync(PluginInitContext context); } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin/IPlugin.cs b/Flow.Launcher.Plugin/IPlugin.cs index 4cc6d8d40..41c61a608 100644 --- a/Flow.Launcher.Plugin/IPlugin.cs +++ b/Flow.Launcher.Plugin/IPlugin.cs @@ -2,10 +2,30 @@ namespace Flow.Launcher.Plugin { + /// + /// Synchronous Plugin Model for Flow Launcher + /// + /// If you assume that Querying or Init method require IO transmission + /// or CPU Intense Job (performing better with cancellation), please try IAsyncPlugin interface + /// + /// public interface IPlugin { + /// + /// Querying when user's search changes + /// + /// This method will be called within a Task.Run, + /// so please avoid synchrously wait for long. + /// + /// + /// Query to search + /// List Query(Query query); - + + /// + /// Initialize plugin + /// + /// void Init(PluginInitContext context); } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs index 9c922f667..434d83646 100644 --- a/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs +++ b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs @@ -2,6 +2,17 @@ namespace Flow.Launcher.Plugin { + /// + /// This interface is to indicate and allow plugins to asyncronously reload their + /// in memory data cache or other mediums when user makes a new change + /// that is not immediately captured. For example, for BrowserBookmark and Program + /// plugin does not automatically detect when a user added a new bookmark or program, + /// so this interface's function is exposed to allow user manually do the reloading after + /// those new additions. + /// + /// The command that allows user to manual reload is exposed via Plugin.Sys, and + /// it will call the plugins that have implemented this interface. + /// public interface IAsyncReloadable { Task ReloadDataAsync(); diff --git a/Flow.Launcher.Plugin/Interfaces/IReloadable.cs b/Flow.Launcher.Plugin/Interfaces/IReloadable.cs index 29b3c15c9..d9160b0ea 100644 --- a/Flow.Launcher.Plugin/Interfaces/IReloadable.cs +++ b/Flow.Launcher.Plugin/Interfaces/IReloadable.cs @@ -1,7 +1,7 @@ namespace Flow.Launcher.Plugin { /// - /// This interface is to indicate and allow plugins to reload their + /// This interface is to indicate and allow plugins to synchronously reload their /// in memory data cache or other mediums when user makes a new change /// that is not immediately captured. For example, for BrowserBookmark and Program /// plugin does not automatically detect when a user added a new bookmark or program, @@ -10,6 +10,10 @@ /// /// The command that allows user to manual reload is exposed via Plugin.Sys, and /// it will call the plugins that have implemented this interface. + /// + /// + /// If requiring reloading data asynchrouly, please try IAsyncReloadable + /// /// public interface IReloadable { From 3d69998bc3fa2e66b53c1fae04a3d865daedcc7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Thu, 14 Jan 2021 19:54:37 +0800 Subject: [PATCH 245/442] fix potential duplicate item due to IResultUpdate event --- Flow.Launcher/ViewModel/MainViewModel.cs | 131 ++++++++++++----------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index cdac8f90d..233611be0 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -94,7 +94,7 @@ namespace Flow.Launcher.ViewModel { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated)pair.Plugin; + var plugin = (IResultUpdated) pair.Plugin; plugin.ResultsUpdated += (s, e) => { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); @@ -113,11 +113,18 @@ namespace Flow.Launcher.ViewModel async Task updateAction() { + var queue = new Dictionary(); while (await _resultsUpdateQueue.OutputAvailableAsync()) { + queue.Clear(); await Task.Delay(20); - _resultsUpdateQueue.TryReceiveAll(out var queue); - UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested)); + while (_resultsUpdateQueue.TryReceive(out var item)) + { + if (!item.Token.IsCancellationRequested) + queue[item.ID] = item; + } + + UpdateResultView(queue.Values); } } @@ -381,7 +388,7 @@ namespace Flow.Launcher.ViewModel Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query { RawQuery = h.Query }, + OriginQuery = new Query {RawQuery = h.Query}, Action = _ => { SelectedResults = Results; @@ -438,70 +445,70 @@ namespace Flow.Launcher.ViewModel var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => - { - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) + { + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) + return; + } + + _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + { + ProgressBarVisibility = Visibility.Visible; + } + }, currentCancellationToken); + + // so looping will stop once it was cancelled + var parallelOptions = new ParallelOptions {CancellationToken = currentCancellationToken}; + try + { + Parallel.ForEach(plugins, parallelOptions, plugin => + { + if (!plugin.Metadata.Disabled) + { + try + { + var results = PluginManager.QueryForPlugin(plugin, query); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, + query, + currentCancellationToken)); + } + catch (Exception e) + { + Log.Exception("MainViewModel", + $"Exception when querying the plugin {plugin.Metadata.Name}", e, + "QueryResults"); + } + } + }); + } + catch (OperationCanceledException) + { + return; + // nothing to do here + } + if (currentCancellationToken.IsCancellationRequested) return; - } - _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + // this should happen once after all queries are done so progress bar should continue + // until the end of all querying + _isQueryRunning = false; + if (!currentCancellationToken.IsCancellationRequested) { - ProgressBarVisibility = Visibility.Visible; + // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; } - }, currentCancellationToken); - - // so looping will stop once it was cancelled - var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; - try - { - Parallel.ForEach(plugins, parallelOptions, plugin => - { - if (!plugin.Metadata.Disabled) - { - try - { - var results = PluginManager.QueryForPlugin(plugin, query); - _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, - query, - currentCancellationToken)); - } - catch (Exception e) - { - Log.Exception("MainViewModel", - $"Exception when querying the plugin {plugin.Metadata.Name}", e, - "QueryResults"); - } - } - }); - } - catch (OperationCanceledException) - { - return; - // nothing to do here - } - - if (currentCancellationToken.IsCancellationRequested) - return; - - // this should happen once after all queries are done so progress bar should continue - // until the end of all querying - _isQueryRunning = false; - if (!currentCancellationToken.IsCancellationRequested) - { - // update to hidden if this is still the current query - ProgressBarVisibility = Visibility.Hidden; - } - }, currentCancellationToken) - .ContinueWith(t => Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults") - , TaskContinuationOptions.OnlyOnFaulted); - + }, currentCancellationToken) + .ContinueWith(t => Log.Exception("MainViewModel", "Error when querying plugins", + t.Exception?.InnerException, "QueryResults") + , TaskContinuationOptions.OnlyOnFaulted); } From 723265a865afa75f7f250ab9f93668298e093d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 15 Jan 2021 10:37:13 +0800 Subject: [PATCH 246/442] fix TopMostRecord and UserSelectedRecord due to System.Text.Json cannot parse private field in version 3.1 --- .../Storage/JsonStorage.cs | 4 +-- Flow.Launcher/Storage/TopMostRecord.cs | 26 ++++++++++++++----- Flow.Launcher/Storage/UserSelectedRecord.cs | 16 +++++++++++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 268dc20b8..f0e4a79fc 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -9,7 +9,7 @@ namespace Flow.Launcher.Infrastructure.Storage /// /// Serialize object using json format. /// - public class JsonStrorage + public class JsonStrorage where T : new() { private readonly JsonSerializerOptions _serializerSettings; private T _data; @@ -76,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Storage BackupOriginFile(); } - _data = JsonSerializer.Deserialize("{}", _serializerSettings); + _data = new T(); Save(); } diff --git a/Flow.Launcher/Storage/TopMostRecord.cs b/Flow.Launcher/Storage/TopMostRecord.cs index 09e69f6d8..c92ef4956 100644 --- a/Flow.Launcher/Storage/TopMostRecord.cs +++ b/Flow.Launcher/Storage/TopMostRecord.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json; +using System.Text.Json.Serialization; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage @@ -8,20 +9,24 @@ namespace Flow.Launcher.Storage // todo this class is not thread safe.... but used from multiple threads. public class TopMostRecord { - private Dictionary records = new Dictionary(); + /// + /// You should not directly access this field + /// + /// It is public due to System.Text.Json limitation in version 3.1 + /// + /// + /// TODO: Set it to private + public Dictionary records { get; set; } = new Dictionary(); internal bool IsTopMost(Result result) { - if (records.Count == 0) + if (records.Count == 0 || !records.ContainsKey(result.OriginQuery.RawQuery)) { return false; } - // since this dictionary should be very small (or empty) going over it should be pretty fast. - return records.Any(o => o.Value.Title == result.Title - && o.Value.SubTitle == result.SubTitle - && o.Value.PluginID == result.PluginID - && o.Key == result.OriginQuery.RawQuery); + // since this dictionary should be very small (or empty) going over it should be pretty fast. + return records[result.OriginQuery.RawQuery].Equals(result); } internal void Remove(Result result) @@ -53,5 +58,12 @@ namespace Flow.Launcher.Storage public string Title { get; set; } public string SubTitle { get; set; } public string PluginID { get; set; } + + public bool Equals(Result r) + { + return Title == r.Title + && SubTitle == r.SubTitle + && PluginID == r.PluginID; + } } } diff --git a/Flow.Launcher/Storage/UserSelectedRecord.cs b/Flow.Launcher/Storage/UserSelectedRecord.cs index c7ffe1a1d..b97138eb9 100644 --- a/Flow.Launcher/Storage/UserSelectedRecord.cs +++ b/Flow.Launcher/Storage/UserSelectedRecord.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text.Json.Serialization; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin; @@ -6,7 +7,20 @@ namespace Flow.Launcher.Storage { public class UserSelectedRecord { - private Dictionary records = new Dictionary(); + /// + /// You should not directly access this field + /// + /// It is public due to System.Text.Json limitation in version 3.1 + /// + /// + /// TODO: Set it to private + [JsonPropertyName("records")] + public Dictionary records { get; set; } + + public UserSelectedRecord() + { + records = new Dictionary(); + } public void Add(Result result) { From 6655a8f168c9d1ae0586b8d52f3ce91fd3f9e321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 15 Jan 2021 15:56:28 +0800 Subject: [PATCH 247/442] optimize code --- Flow.Launcher/Storage/UserSelectedRecord.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/Storage/UserSelectedRecord.cs b/Flow.Launcher/Storage/UserSelectedRecord.cs index b97138eb9..bc7a2da73 100644 --- a/Flow.Launcher/Storage/UserSelectedRecord.cs +++ b/Flow.Launcher/Storage/UserSelectedRecord.cs @@ -25,9 +25,9 @@ namespace Flow.Launcher.Storage public void Add(Result result) { var key = result.ToString(); - if (records.TryGetValue(key, out int value)) + if (records.ContainsKey(key)) { - records[key] = value + 1; + records[key]++; } else { From 5688f377e03fc8f58c1baba1934a164ef6b493f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 16 Jan 2021 02:34:26 +0800 Subject: [PATCH 248/442] Stop ProgressBar animation when Flow is hidden Co-Authored-By: imsh <9258159+imsh@users.noreply.github.com> --- Flow.Launcher/MainWindow.xaml | 2 +- Flow.Launcher/MainWindow.xaml.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index a2cfe569d..4cc0b4428 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -97,7 +97,7 @@ Background="Transparent"/> diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 3812b4e1f..87155ea17 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -84,8 +84,15 @@ namespace Flow.Launcher QueryTextBox.SelectAll(); _viewModel.LastQuerySelected = true; } + + ProgressBar.BeginStoryboard(_progressBarStoryboard); + } + else + { + _progressBarStoryboard.Stop(); } } + }; _settings.PropertyChanged += (o, e) => { From 114c12bdaf694dd2b5f98fc59b14ff1d1b84c0ad Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 17 Jan 2021 18:47:19 +1100 Subject: [PATCH 249/442] formatting and description --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- Flow.Launcher.Plugin/IAsyncPlugin.cs | 4 ++++ Flow.Launcher.Plugin/IPlugin.cs | 4 ++-- Flow.Launcher.Plugin/Interfaces/IReloadable.cs | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 9 +++++++-- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 1 - 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 5a5ba1971..26167e945 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -320,4 +320,4 @@ namespace Flow.Launcher.Core.Plugin } } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/IAsyncPlugin.cs b/Flow.Launcher.Plugin/IAsyncPlugin.cs index 3e1a086bc..c6f11aead 100644 --- a/Flow.Launcher.Plugin/IAsyncPlugin.cs +++ b/Flow.Launcher.Plugin/IAsyncPlugin.cs @@ -12,6 +12,10 @@ namespace Flow.Launcher.Plugin /// /// Asynchronous Querying /// + /// + /// If the Querying or Init method requires high IO transmission + /// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncPlugin interface + /// /// Query to search /// Cancel when querying job is obsolete /// diff --git a/Flow.Launcher.Plugin/IPlugin.cs b/Flow.Launcher.Plugin/IPlugin.cs index 41c61a608..208f3021b 100644 --- a/Flow.Launcher.Plugin/IPlugin.cs +++ b/Flow.Launcher.Plugin/IPlugin.cs @@ -5,8 +5,8 @@ namespace Flow.Launcher.Plugin /// /// Synchronous Plugin Model for Flow Launcher /// - /// If you assume that Querying or Init method require IO transmission - /// or CPU Intense Job (performing better with cancellation), please try IAsyncPlugin interface + /// If the Querying or Init method requires high IO transmission + /// or performaing CPU intense jobs (performing better with cancellation), please try the IAsyncPlugin interface /// /// public interface IPlugin diff --git a/Flow.Launcher.Plugin/Interfaces/IReloadable.cs b/Flow.Launcher.Plugin/Interfaces/IReloadable.cs index d9160b0ea..31611519c 100644 --- a/Flow.Launcher.Plugin/Interfaces/IReloadable.cs +++ b/Flow.Launcher.Plugin/Interfaces/IReloadable.cs @@ -12,7 +12,7 @@ /// it will call the plugins that have implemented this interface. /// /// - /// If requiring reloading data asynchrouly, please try IAsyncReloadable + /// If requiring reloading data asynchronously, please use the IAsyncReloadable interface /// /// public interface IReloadable diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index bcb257204..a062f59dc 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -408,8 +408,13 @@ namespace Flow.Launcher.ViewModel for (var i = 0; i < plugins.Count; i++) { if (!plugins[i].Metadata.Disabled) + { tasks[i] = QueryTask(plugins[i], query, currentCancellationToken); - else tasks[i] = Task.CompletedTask; // Avoid Null + } + else + { + tasks[i] = Task.CompletedTask; // Avoid Null + } } // Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first @@ -429,7 +434,7 @@ namespace Flow.Launcher.ViewModel ProgressBarVisibility = Visibility.Hidden; } - // Local Function + // Local function async Task QueryTask(PluginPair plugin, Query query, CancellationToken token) { // Since it is wrapped within a Task.Run, the synchronous context is null diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 3253b7a7b..88d7d6927 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Windows; namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { From d741a98112a50b81cf4551037b1d6bad6c5b0be7 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 17 Jan 2021 18:47:43 +1100 Subject: [PATCH 250/442] fixed last index time during init --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 954c238a9..c283faa83 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -97,22 +97,31 @@ namespace Flow.Launcher.Plugin.Program Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); }); + bool indexedWinApps = false; + bool indexedUWPApps = false; var a = Task.Run(() => { if (IsStartupIndexProgramsRequired || !_win32s.Any()) + { Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs); + indexedWinApps = true; + } }); var b = Task.Run(() => { if (IsStartupIndexProgramsRequired || !_uwps.Any()) + { Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms); + indexedUWPApps = true; + } }); await Task.WhenAll(a, b); - _settings.LastIndexTime = DateTime.Today; + if (indexedWinApps && indexedUWPApps) + _settings.LastIndexTime = DateTime.Today; } public static void IndexWin32Programs() From 969265c24fb617f018ddf38285fb61ec1ac2274b Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 17 Jan 2021 19:05:28 +1100 Subject: [PATCH 251/442] fix formatting --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 12 +++++++----- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 3 +-- .../SuggestionSources/Bing.cs | 6 +++++- .../SuggestionSources/Google.cs | 6 +++++- .../SuggestionSources/SuggestionSource.cs | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index c283faa83..d7413874b 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -202,12 +202,14 @@ namespace Flow.Launcher.Plugin.Program return; if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = - false; + _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) + .FirstOrDefault() + .Enabled = false; if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = - false; + _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) + .FirstOrDefault() + .Enabled = false; _settings.DisabledProgramSources .Add( @@ -241,4 +243,4 @@ namespace Flow.Launcher.Plugin.Program await IndexPrograms(); } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 7ebab91a1..624fe05bc 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -170,8 +170,7 @@ namespace Flow.Launcher.Plugin.Sys // http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html // FYI, couldn't find documentation for this but if the recycle bin is already empty, it will return -2147418113 (0x8000FFFF (E_UNEXPECTED)) // 0 for nothing - var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, - 0); + var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0); if (result != (uint) HRESULT.S_OK && result != (uint) 0x8000FFFF) { MessageBox.Show($"Error emptying recycle bin, error code: {result}\n" + diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index 81725c3f2..ffde2fda2 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -22,8 +22,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources try { const string api = "https://api.bing.com/qsonhs.aspx?q="; + using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); - if (resultStream.Length == 0) return new List(); // this handles the cancellation + + if (resultStream.Length == 0) + return new List(); // this handles the cancellation + json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token)).RootElement.GetProperty("AS"); } diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index b150d26c1..c33ebd7e1 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -21,8 +21,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources try { const string api = "https://www.google.com/complete/search?output=chrome&q="; + using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); - if (resultStream.Length == 0) return new List(); + + if (resultStream.Length == 0) + return new List(); + json = await JsonDocument.ParseAsync(resultStream); } diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs index bf444a2f7..c58e61141 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs @@ -8,4 +8,4 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { public abstract Task> Suggestions(string query, CancellationToken token); } -} \ No newline at end of file +} From 539f4bf4c46ca574a50dbb3f924b1b5160da7084 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 17 Jan 2021 19:10:26 +1100 Subject: [PATCH 252/442] add eol --- Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs | 2 +- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 2 +- Flow.Launcher.Plugin/IAsyncPlugin.cs | 2 +- Flow.Launcher.Plugin/IPlugin.cs | 2 +- Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs index 1a1b17539..273698b86 100644 --- a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs @@ -54,4 +54,4 @@ namespace Flow.Launcher.Core.Plugin return referencedPluginPackageDependencyResolver.ResolveAssemblyToPath(assemblyName) != null; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index b18c07e3c..fcf178445 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -183,4 +183,4 @@ namespace Flow.Launcher.Core.Plugin }); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/IAsyncPlugin.cs b/Flow.Launcher.Plugin/IAsyncPlugin.cs index c6f11aead..b0b41cc22 100644 --- a/Flow.Launcher.Plugin/IAsyncPlugin.cs +++ b/Flow.Launcher.Plugin/IAsyncPlugin.cs @@ -28,4 +28,4 @@ namespace Flow.Launcher.Plugin /// Task InitAsync(PluginInitContext context); } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/IPlugin.cs b/Flow.Launcher.Plugin/IPlugin.cs index 208f3021b..203dc9af7 100644 --- a/Flow.Launcher.Plugin/IPlugin.cs +++ b/Flow.Launcher.Plugin/IPlugin.cs @@ -28,4 +28,4 @@ namespace Flow.Launcher.Plugin /// void Init(PluginInitContext context); } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs index 434d83646..fc4ac4715 100644 --- a/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs +++ b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs @@ -17,4 +17,4 @@ namespace Flow.Launcher.Plugin { Task ReloadDataAsync(); } -} \ No newline at end of file +} From 4b9936bc90a12a3a177117d83f83ea22760e3acb Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 17 Jan 2021 19:26:34 +1100 Subject: [PATCH 253/442] Version bump Flow.Launcher.Plugin Version bump Flow.Launcher.Plugin --- Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index b7b52ac35..698802ba3 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -14,10 +14,10 @@ - 1.3.1 - 1.3.1 - 1.3.1 - 1.3.1 + 1.4.0 + 1.4.0 + 1.4.0 + 1.4.0 Flow.Launcher.Plugin Flow-Launcher MIT @@ -64,4 +64,4 @@ - \ No newline at end of file + From 3abc77f391d48b0ba15e1c65b4e9722149470b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:05:31 +0800 Subject: [PATCH 254/442] Merge remote-tracking branch 'upstream/dev' into RenderImprovement # Conflicts: # Flow.Launcher/ViewModel/MainViewModel.cs --- Flow.Launcher/ViewModel/MainViewModel.cs | 87 ++++++++---------------- 1 file changed, 28 insertions(+), 59 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 139d89169..35a7302f4 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -244,7 +244,7 @@ namespace Flow.Launcher.ViewModel public string QueryText { - get { return _queryText; } + get => _queryText; set { _queryText = value; @@ -444,28 +444,26 @@ namespace Flow.Launcher.ViewModel var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(async () => - { - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested) - return; - } - - _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { - ProgressBarVisibility = Visibility.Visible; + // Wait 45 millisecond for query change in global query + // if query changes, return so that it won't be calculated + await Task.Delay(45, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) + return; } - }, currentCancellationToken); - var plugins = PluginManager.ValidPluginsForQuery(query); - Task.Run(async () => - { + _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + { + ProgressBarVisibility = Visibility.Visible; + } + }, currentCancellationToken); + + var plugins = PluginManager.ValidPluginsForQuery(query); // so looping will stop once it was cancelled Task[] tasks = new Task[plugins.Count]; @@ -475,7 +473,7 @@ namespace Flow.Launcher.ViewModel { if (!plugins[i].Metadata.Disabled) { - tasks[i] = QueryTask(plugins[i], query, currentCancellationToken); + tasks[i] = QueryTask(plugins[i]); } else { @@ -491,6 +489,9 @@ namespace Flow.Launcher.ViewModel // nothing to do here } + if (currentCancellationToken.IsCancellationRequested) + return; + // this should happen once after all queries are done so progress bar should continue // until the end of all querying _isQueryRunning = false; @@ -501,28 +502,20 @@ namespace Flow.Launcher.ViewModel } // Local function - async Task QueryTask(PluginPair plugin, Query query, CancellationToken token) + async Task QueryTask(PluginPair plugin) { // Since it is wrapped within a Task.Run, the synchronous context is null // Task.Yield will force it to run in ThreadPool await Task.Yield(); - var results = await PluginManager.QueryForPlugin(plugin, query, token); + var results = await PluginManager.QueryForPlugin(plugin, query, currentCancellationToken); if (!currentCancellationToken.IsCancellationRequested) - UpdateResultView(results, plugin.Metadata, query); + _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, + currentCancellationToken)); } - }, currentCancellationToken).ContinueWith( - t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), + }, currentCancellationToken) + .ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), TaskContinuationOptions.OnlyOnFaulted); - - - else - - { - Results.Clear(); - Results.Visbility = Visibility.Collapsed; - } - } } @@ -786,7 +779,7 @@ namespace Flow.Launcher.ViewModel } else { - var priorityScore = metaResults.Metadata.Priority * 50; + var priorityScore = metaResults.Metadata.Priority * 150; result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; } } @@ -795,30 +788,6 @@ namespace Flow.Launcher.ViewModel Results.AddResults(resultsForUpdates, token); } - /// U - /// To avoid deadlock, this method should not called from main thread - /// - public void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) - { - foreach (var result in list) - { - if (_topMostRecord.IsTopMost(result)) - { - result.Score = int.MaxValue; - } - else - { - var priorityScore = metadata.Priority * 150; - result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore; - } - } - - if (originQuery.RawQuery == _lastQuery.RawQuery) - { - Results.AddResults(list, metadata.ID); - } - } - #endregion } } \ No newline at end of file From 1071f75633305e41d27f155a9f60d359604f386d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:06:57 +0800 Subject: [PATCH 255/442] Remove unused using --- Flow.Launcher/ViewModel/MainViewModel.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 35a7302f4..2dcbfc004 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,9 +17,6 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; -using System.Windows.Media; -using Flow.Launcher.Infrastructure.Image; -using System.Collections.Concurrent; using Flow.Launcher.Infrastructure.Logger; using System.Threading.Tasks.Dataflow; From 501124609038dea1e4d06f6194edbdd5f71fbb5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:10:36 +0800 Subject: [PATCH 256/442] Add scoring to ContextMenu search --- Flow.Launcher/ViewModel/MainViewModel.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2dcbfc004..454d934a3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -354,9 +354,20 @@ namespace Flow.Launcher.ViewModel { var filtered = results.Where ( - r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() - || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() - ).ToList(); + r => + { + var match = StringMatcher.FuzzySearch(query, r.Title); + if (!match.IsSearchPrecisionScoreMet()) + { + match = StringMatcher.FuzzySearch(query, r.SubTitle); + } + + if (!match.IsSearchPrecisionScoreMet()) return false; + + r.Score = match.Score; + return true; + + }).ToList(); ContextMenu.AddResults(filtered, id); } else From e2e3f4a365acef01ca9fc182bc91543c49f15adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:21:35 +0800 Subject: [PATCH 257/442] remove extra query building due to merge --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 454d934a3..8195c745a 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -469,9 +469,6 @@ namespace Flow.Launcher.ViewModel } }, currentCancellationToken); - var plugins = PluginManager.ValidPluginsForQuery(query); - // so looping will stop once it was cancelled - Task[] tasks = new Task[plugins.Count]; try { From bf96a60862307c41e91bb960a874c15ed1818cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:39:53 +0800 Subject: [PATCH 258/442] Only wait PluginManifest downloading for 500ms to allow Flow to be used even without internet connection. --- .../Main.cs | 17 +++- .../PluginsManager.cs | 81 +++++++++++-------- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 40579e6e5..1bea7d1be 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -37,8 +37,17 @@ namespace Flow.Launcher.Plugin.PluginsManager Settings = viewModel.Settings; contextMenu = new ContextMenu(Context); pluginManager = new PluginsManager(Context, Settings); - await pluginManager.UpdateManifest(); - lastUpdateTime = DateTime.Now; + var updateManifestTask = pluginManager.UpdateManifest(); + if (await Task.WhenAny(updateManifestTask, Task.Delay(500)) == updateManifestTask) + { + lastUpdateTime = DateTime.Now; + } + else + { + context.API.ShowMsg("Plugin Manifest Download Fail.", + @"Please check internet transmission with Github.com. + You may not be able to Install and Update Plugin.", pluginManager.icoPath); + } } public List LoadContextMenus(Result selectedResult) @@ -61,7 +70,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return search switch { - var s when s.StartsWith(Settings.HotKeyInstall) => pluginManager.RequestInstallOrUpdate(s), + var s when s.StartsWith(Settings.HotKeyInstall) => await pluginManager.RequestInstallOrUpdate(s), var s when s.StartsWith(Settings.HotkeyUninstall) => pluginManager.RequestUninstall(s), var s when s.StartsWith(Settings.HotkeyUpdate) => pluginManager.RequestUpdate(s), _ => pluginManager.GetDefaultHotKeys().Where(hotkey => @@ -93,4 +102,4 @@ namespace Flow.Launcher.Plugin.PluginsManager lastUpdateTime = DateTime.Now; } } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 68df5bc1f..a02cce339 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -36,7 +37,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } } - private readonly string icoPath = "Images\\pluginsmanager.png"; + internal readonly string icoPath = "Images\\pluginsmanager.png"; internal PluginsManager(PluginInitContext context, Settings settings) { @@ -64,27 +65,27 @@ namespace Flow.Launcher.Plugin.PluginsManager return false; } }, - new Result() + new Result() + { + Title = Settings.HotkeyUninstall, + IcoPath = icoPath, + Action = _ => { - Title = Settings.HotkeyUninstall, - IcoPath = icoPath, - Action = _ => - { - Context.API.ChangeQuery("pm uninstall "); - return false; - } - }, - new Result() - { - Title = Settings.HotkeyUpdate, - IcoPath = icoPath, - Action = _ => - { - Context.API.ChangeQuery("pm update "); - return false; - } + Context.API.ChangeQuery("pm uninstall "); + return false; } - }; + }, + new Result() + { + Title = Settings.HotkeyUpdate, + IcoPath = icoPath, + Action = _ => + { + Context.API.ChangeQuery("pm update "); + return false; + } + } + }; } internal async Task InstallOrUpdate(UserPlugin plugin) @@ -137,7 +138,8 @@ namespace Flow.Launcher.Plugin.PluginsManager catch (Exception e) { Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), plugin.Name)); + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), + plugin.Name)); Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "InstallOrUpdate"); @@ -164,7 +166,8 @@ namespace Flow.Launcher.Plugin.PluginsManager from existingPlugin in Context.API.GetAllPlugins() join pluginFromManifest in pluginsManifest.UserPlugins on existingPlugin.Metadata.ID equals pluginFromManifest.ID - where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) < 0 // if current version precedes manifest version + where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) < + 0 // if current version precedes manifest version select new { @@ -214,22 +217,29 @@ namespace Flow.Launcher.Plugin.PluginsManager Task.Run(async delegate { - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), + Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); - await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false); + await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) + .ConfigureAwait(false); - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - Context.API.GetTranslation("plugin_pluginsmanager_download_success")); + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), + Context.API.GetTranslation("plugin_pluginsmanager_download_success")); Install(x.PluginNewUserPlugin, downloadToFilePath); Context.API.RestartApp(); }).ContinueWith(t => { - Log.Exception("PluginsManager", $"Update failed for {x.Name}", t.Exception.InnerException, "RequestUpdate"); - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), x.Name)); + Log.Exception("PluginsManager", $"Update failed for {x.Name}", + t.Exception.InnerException, "RequestUpdate"); + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), + x.Name)); }, TaskContinuationOptions.OnlyOnFaulted); return true; @@ -264,8 +274,15 @@ namespace Flow.Launcher.Plugin.PluginsManager .ToList(); } - internal List RequestInstallOrUpdate(string searchName) + private Task _downloadManifestTask = Task.CompletedTask; + + internal async ValueTask> RequestInstallOrUpdate(string searchName) { + if (!pluginsManifest.UserPlugins.Any() && _downloadManifestTask.IsCompleted) + _downloadManifestTask = pluginsManifest.DownloadManifest(); + + await _downloadManifestTask; + var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty).Trim(); var results = @@ -406,4 +423,4 @@ namespace Flow.Launcher.Plugin.PluginsManager return new List(); } } -} +} \ No newline at end of file From d203a7a5f3d374b62a752878b4156be2de095c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:42:01 +0800 Subject: [PATCH 259/442] Add Cancellation Token --- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 2 +- .../PluginsManager.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 1bea7d1be..1d473a82a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -70,7 +70,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return search switch { - var s when s.StartsWith(Settings.HotKeyInstall) => await pluginManager.RequestInstallOrUpdate(s), + var s when s.StartsWith(Settings.HotKeyInstall) => await pluginManager.RequestInstallOrUpdate(s, token), var s when s.StartsWith(Settings.HotkeyUninstall) => pluginManager.RequestUninstall(s), var s when s.StartsWith(Settings.HotkeyUpdate) => pluginManager.RequestUpdate(s), _ => pluginManager.GetDefaultHotKeys().Where(hotkey => diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index a02cce339..06e97438e 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -276,13 +276,18 @@ namespace Flow.Launcher.Plugin.PluginsManager private Task _downloadManifestTask = Task.CompletedTask; - internal async ValueTask> RequestInstallOrUpdate(string searchName) + internal async ValueTask> RequestInstallOrUpdate(string searchName, CancellationToken token) { - if (!pluginsManifest.UserPlugins.Any() && _downloadManifestTask.IsCompleted) + if (!pluginsManifest.UserPlugins.Any() && + _downloadManifestTask.IsCompleted || + _downloadManifestTask.IsFaulted) _downloadManifestTask = pluginsManifest.DownloadManifest(); await _downloadManifestTask; + if (token.IsCancellationRequested) + return null; + var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty).Trim(); var results = From 415adb72bf8209bea6b18f5e64a4af9d83fb4425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 18:46:08 +0800 Subject: [PATCH 260/442] Optimize Logic --- .../Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 06e97438e..0f5e6d9e8 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -279,15 +279,16 @@ namespace Flow.Launcher.Plugin.PluginsManager internal async ValueTask> RequestInstallOrUpdate(string searchName, CancellationToken token) { if (!pluginsManifest.UserPlugins.Any() && - _downloadManifestTask.IsCompleted || - _downloadManifestTask.IsFaulted) + _downloadManifestTask.Status != TaskStatus.Running) + { _downloadManifestTask = pluginsManifest.DownloadManifest(); + } await _downloadManifestTask; if (token.IsCancellationRequested) return null; - + var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty).Trim(); var results = From d35d7ae2d6891bcb074fc58849a047513ff8cb97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 17 Jan 2021 23:45:15 +0800 Subject: [PATCH 261/442] Remove unused catch and optimize code --- .../Programs/Win32.cs | 119 ++++++++---------- 1 file changed, 54 insertions(+), 65 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index e7a7c2bd7..b6ca7ffcc 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -16,7 +16,6 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program.Programs { - [Serializable] public class Win32 : IProgram { @@ -127,19 +126,19 @@ namespace Flow.Launcher.Plugin.Program.Programs Action = _ => { var args = !string.IsNullOrWhiteSpace(Main._settings.CustomizedArgs) - ? Main._settings.CustomizedArgs - .Replace("%s",$"\"{ParentDirectory}\"") - .Replace("%f",$"\"{FullPath}\"") - : Main._settings.CustomizedExplorer==Settings.Explorer - ? $"/select,\"{FullPath}\"" - : Settings.ExplorerArgs; + ? Main._settings.CustomizedArgs + .Replace("%s", $"\"{ParentDirectory}\"") + .Replace("%f", $"\"{FullPath}\"") + : Main._settings.CustomizedExplorer == Settings.Explorer + ? $"/select,\"{FullPath}\"" + : Settings.ExplorerArgs; Main.StartProcess(Process.Start, - new ProcessStartInfo( - !string.IsNullOrWhiteSpace(Main._settings.CustomizedExplorer) - ? Main._settings.CustomizedExplorer - : Settings.Explorer, - args)); + new ProcessStartInfo( + !string.IsNullOrWhiteSpace(Main._settings.CustomizedExplorer) + ? Main._settings.CustomizedExplorer + : Settings.Explorer, + args)); return true; }, @@ -150,7 +149,6 @@ namespace Flow.Launcher.Plugin.Program.Programs } - public override string ToString() { return ExecutableName; @@ -176,9 +174,9 @@ namespace Flow.Launcher.Plugin.Program.Programs catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) { ProgramLogger.LogException($"|Win32|Win32Program|{path}" + - $"|Permission denied when trying to load the program from {path}", e); + $"|Permission denied when trying to load the program from {path}", e); - return new Win32() { Valid = false, Enabled = false }; + return new Win32() {Valid = false, Enabled = false}; } } @@ -216,13 +214,15 @@ namespace Flow.Launcher.Plugin.Program.Programs } } } + return program; } catch (COMException e) { // C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception ProgramLogger.LogException($"|Win32|LnkProgram|{path}" + - "|Error caused likely due to trying to get the description of the program", e); + "|Error caused likely due to trying to get the description of the program", + e); program.Valid = false; return program; @@ -252,37 +252,22 @@ namespace Flow.Launcher.Plugin.Program.Programs catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) { ProgramLogger.LogException($"|Win32|ExeProgram|{path}" + - $"|Permission denied when trying to load the program from {path}", e); + $"|Permission denied when trying to load the program from {path}", e); - return new Win32() { Valid = false, Enabled = false }; + return new Win32() {Valid = false, Enabled = false}; } } private static IEnumerable ProgramPaths(string directory, string[] suffixes) { if (!Directory.Exists(directory)) - return new string[] { }; - try - { - var paths = Directory.EnumerateFiles(directory, "*", new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = true - }) - .Where(x => suffixes.Contains(Extension(x))); + return Enumerable.Empty(); - return paths; - } - catch (DirectoryNotFoundException e) + return Directory.EnumerateFiles(directory, "*", new EnumerationOptions { - ProgramLogger.LogException($"Directory not found {directory}", e); - return new string[] { }; - } - catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) - { - ProgramLogger.LogException($"Permission denied {directory}", e); - return new string[] { }; - } + IgnoreInaccessible = true, + RecurseSubdirectories = true + }).Where(x => suffixes.Contains(Extension(x))); } private static string Extension(string path) @@ -328,13 +313,13 @@ namespace Flow.Launcher.Plugin.Program.Programs var toFilter = paths1.Concat(paths2); var programs = toFilter - .Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1)) - .Distinct() - .Select(x => Extension(x) switch - { - ShortcutExtension => LnkProgram(x), - _ => Win32Program(x) - }).Where(x => x.Valid); + .Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1)) + .Distinct() + .Select(x => Extension(x) switch + { + ShortcutExtension => LnkProgram(x), + _ => Win32Program(x) + }).Where(x => x.Valid); return programs; } @@ -351,6 +336,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { programs = programs.Concat(GetProgramsFromRegistry(rootMachine)); } + if (rootUser != null) { programs = programs.Concat(GetProgramsFromRegistry(rootUser)); @@ -360,7 +346,9 @@ namespace Flow.Launcher.Plugin.Program.Programs var disabledProgramsList = Main._settings.DisabledProgramSources; var toFilter = programs.Where(p => suffixes.Contains(Extension(p.ExecutableName))); - var filtered = toFilter.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)).Select(t1 => t1); + var filtered = toFilter + .Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) + .Select(t1 => t1); return filtered.ToList(); } @@ -368,10 +356,10 @@ namespace Flow.Launcher.Plugin.Program.Programs private static IEnumerable GetProgramsFromRegistry(RegistryKey root) { return root - .GetSubKeyNames() - .Select(x => GetProgramPathFromRegistrySubKeys(root, x)) - .Distinct() - .Select(x => GetProgramFromPath(x)); + .GetSubKeyNames() + .Select(x => GetProgramPathFromRegistrySubKeys(root, x)) + .Distinct() + .Select(GetProgramFromPath); } private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string subkey) @@ -397,7 +385,7 @@ namespace Flow.Launcher.Plugin.Program.Programs catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) { ProgramLogger.LogException($"|Win32|GetProgramPathFromRegistrySubKeys|{path}" + - $"|Permission denied when trying to load the program from {path}", e); + $"|Permission denied when trying to load the program from {path}", e); return string.Empty; } @@ -419,13 +407,13 @@ namespace Flow.Launcher.Plugin.Program.Programs return entry; } - private class Win32ComparatorBasedonDescription : IEqualityComparer + private class Win32ComparatorWithDescription : IEqualityComparer { - public readonly static Win32ComparatorBasedonDescription Default = new Win32ComparatorBasedonDescription(); + public static readonly Win32ComparatorWithDescription Default = new Win32ComparatorWithDescription(); public bool Equals(Win32 x, Win32 y) { - return x.Description == y.Description; + return x?.Description == y?.Description; } public int GetHashCode(Win32 obj) @@ -435,16 +423,16 @@ namespace Flow.Launcher.Plugin.Program.Programs } private static Win32[] ProgramsHasher(IEnumerable programs) - => programs.GroupBy(p => p.FullPath.ToLower()) - .SelectMany(g => - { - var tempList = g.ToList(); - if (tempList.Count() > 1) - return g.Where(p => !string.IsNullOrEmpty(p.Description)) - .Distinct(Win32ComparatorBasedonDescription.Default); - else - return g.Take(1); - }).ToArray(); + { + return programs.GroupBy(p => p.FullPath.ToLower()) + .SelectMany(g => + { + if (g.Count() > 1) + return g.Where(p => !string.IsNullOrEmpty(p.Description)) + .Distinct(Win32ComparatorWithDescription.Default); + return g; + }).ToArray(); + } public static Win32[] All(Settings settings) @@ -467,6 +455,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var startMenu = StartMenuPrograms(settings.ProgramSuffixes); programs = programs.Concat(startMenu); } + return ProgramsHasher(programs); } #if DEBUG //This is to make developer aware of any unhandled exception and add in handling. @@ -486,4 +475,4 @@ namespace Flow.Launcher.Plugin.Program.Programs #endif } } -} +} \ No newline at end of file From 2fecfa2644d7675ff527b978299945b12d0f20d3 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 18 Jan 2021 07:44:43 +1100 Subject: [PATCH 262/442] verison bump plugins manager --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 60ab7ab18..3accd4fb9 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.5.0", + "Version": "1.5.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From ff26bccba5c27daea0222c44b9937461c094c0d8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 18 Jan 2021 07:58:38 +1100 Subject: [PATCH 263/442] version bump plugins --- Plugins/Flow.Launcher.Plugin.Explorer/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Program/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Sys/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index aa44c4413..68ab9f562 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -7,7 +7,7 @@ "Name": "Explorer", "Description": "Search and manage files and folders. Explorer utilises Windows Index Search", "Author": "Jeremy Wu", - "Version": "1.2.6", + "Version": "1.3.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 3accd4fb9..7f3f4a5cf 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.5.1", + "Version": "1.6.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index 6c2c18e47..1dedd9909 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "1.2.3", + "Version": "1.3.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json index cf8ed6041..75d7073b8 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json @@ -4,7 +4,7 @@ "Name": "System Commands", "Description": "Provide System related commands. e.g. shutdown,lock,setting etc.", "Author": "qianlifeng", - "Version": "1.1.2", + "Version": "1.2.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll", diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json index c036fbf8b..2696ab622 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json @@ -25,7 +25,7 @@ "Name": "Web Searches", "Description": "Provide the web search ability", "Author": "qianlifeng", - "Version": "1.2.1", + "Version": "1.3.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll", From 2643c7d7302d76b0d8737b29d0b456ca7f24cb2b Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 Jan 2021 07:05:30 +1100 Subject: [PATCH 264/442] exclude index search on special chars --- .../Search/WindowsIndex/IndexSearch.cs | 4 ++-- Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs index 5b1d47ef8..06059ceea 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.Logger; using Microsoft.Search.Interop; using System; using System.Collections.Generic; @@ -15,7 +15,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex private readonly ResultManager resultManager; // Reserved keywords in oleDB - private readonly string reservedStringPattern = @"^[\/\\\$\%_]+$"; + private readonly string reservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_]+$"; internal IndexSearch(PluginInitContext context) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index e62ea93fc..e62637896 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,7 +1,6 @@ using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using System.Collections.Generic; -using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.Explorer { From ed1bf0e956fe2b713d2715252a6636a98ecc857e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 Jan 2021 07:07:06 +1100 Subject: [PATCH 265/442] version bump Explorer plugin --- Plugins/Flow.Launcher.Plugin.Explorer/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index aa44c4413..49baa5090 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -7,7 +7,7 @@ "Name": "Explorer", "Description": "Search and manage files and folders. Explorer utilises Windows Index Search", "Author": "Jeremy Wu", - "Version": "1.2.6", + "Version": "1.2.7", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", From 2463582af01e6b7a4f8234bd0a121406733afcc0 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 Jan 2021 08:52:12 +1100 Subject: [PATCH 266/442] fix error message not working + increase delay --- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 1d473a82a..932248de4 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -38,15 +38,15 @@ namespace Flow.Launcher.Plugin.PluginsManager contextMenu = new ContextMenu(Context); pluginManager = new PluginsManager(Context, Settings); var updateManifestTask = pluginManager.UpdateManifest(); - if (await Task.WhenAny(updateManifestTask, Task.Delay(500)) == updateManifestTask) + if (await Task.WhenAny(updateManifestTask, Task.Delay(1000)) == updateManifestTask) { lastUpdateTime = DateTime.Now; } else { context.API.ShowMsg("Plugin Manifest Download Fail.", - @"Please check internet transmission with Github.com. - You may not be able to Install and Update Plugin.", pluginManager.icoPath); + "Please check if you can connect to github.com. "+ + "This error means you may not be able to Install and Update Plugin.", pluginManager.icoPath, false); } } From d7abae1ab22166eabf940d3983400aadd9ba93d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Tue, 19 Jan 2021 10:08:37 +0800 Subject: [PATCH 267/442] Add Cancellation token for file system enumeration --- .../DirectoryInfo/DirectoryInfoSearch.cs | 42 +++++++++++-------- .../Search/SearchManager.cs | 20 ++++----- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 88d7d6927..efdb5a6fe 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { @@ -16,14 +17,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo resultManager = new ResultManager(context); } - internal List TopLevelDirectorySearch(Query query, string search) + internal List TopLevelDirectorySearch(Query query, string search, CancellationToken token) { var criteria = ConstructSearchCriteria(search); - if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator)) - return DirectorySearch(SearchOption.AllDirectories, query, search, criteria); + if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > + search.LastIndexOf(Constants.DirectorySeperator)) + return DirectorySearch(new EnumerationOptions + { + RecurseSubdirectories = true + }, query, search, criteria, token); - return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria); + return DirectorySearch(null, query, search, criteria, token); // null will be passed as default } public string ConstructSearchCriteria(string search) @@ -45,7 +50,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo return incompleteName; } - private List DirectorySearch(SearchOption searchOption, Query query, string search, string searchCriteria) + private List DirectorySearch(EnumerationOptions enumerationOption, Query query, string search, + string searchCriteria, CancellationToken token) { var results = new List(); @@ -58,38 +64,38 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { var directoryInfo = new System.IO.DirectoryInfo(path); - foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, searchOption)) + foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption)) { - if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue; - if (fileSystemInfo is System.IO.DirectoryInfo) { - folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false)); + folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, + fileSystemInfo.FullName, query, true, false)); } else { fileList.Add(resultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false)); } + + if (token.IsCancellationRequested) + return null; } } catch (Exception e) { - if (e is UnauthorizedAccessException || e is ArgumentException) - { - results.Add(new Result { Title = e.Message, Score = 501 }); + if (!(e is ArgumentException)) throw e; + + results.Add(new Result {Title = e.Message, Score = 501}); - return results; - } + return results; #if DEBUG // Please investigate and handle error from DirectoryInfo search - throw e; #else Log.Exception($"|Flow.Launcher.Plugin.Explorer.DirectoryInfoSearch|Error from performing DirectoryInfoSearch", e); -#endif +#endif } - // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. + // Initial 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(); } } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 6b3a96912..d51833a6f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -81,12 +81,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search return null; results.AddRange(await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, - DirectoryInfoClassSearch, - useIndexSearch, - query, - locationPath, - token).ConfigureAwait(false)); - + DirectoryInfoClassSearch, + useIndexSearch, + query, + locationPath, + token).ConfigureAwait(false)); + return results; } @@ -109,23 +109,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search return actionKeyword == settings.FileContentSearchActionKeyword; } - private List DirectoryInfoClassSearch(Query query, string querySearch) + private List DirectoryInfoClassSearch(Query query, string querySearch, CancellationToken token) { var directoryInfoSearch = new DirectoryInfoSearch(context); - return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch); + return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token); } public async Task> TopLevelDirectorySearchBehaviourAsync( Func>> windowsIndexSearch, - Func> directoryInfoClassSearch, + Func> directoryInfoClassSearch, bool useIndexSearch, Query query, string querySearchString, CancellationToken token) { if (!useIndexSearch) - return directoryInfoClassSearch(query, querySearchString); + return directoryInfoClassSearch(query, querySearchString, token); return await windowsIndexSearch(query, querySearchString, token); } From 07309568226695e73f46e44d38531385b9e1405f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 19 Jan 2021 22:42:32 +0800 Subject: [PATCH 268/442] Make pause and resume when progressbarvisibility changed. --- Flow.Launcher/MainWindow.xaml.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 87155ea17..114b6cd7f 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -52,7 +52,7 @@ namespace Flow.Launcher private void OnInitialized(object sender, EventArgs e) { - + } private void OnLoaded(object sender, RoutedEventArgs _) @@ -85,14 +85,27 @@ namespace Flow.Launcher _viewModel.LastQuerySelected = true; } - ProgressBar.BeginStoryboard(_progressBarStoryboard); } else { - _progressBarStoryboard.Stop(); } } - + else if (e.PropertyName == nameof(MainViewModel.ProgressBarVisibility)) + { + Dispatcher.Invoke(() => + { + if (ProgressBar.Visibility == Visibility.Hidden) + { + _progressBarStoryboard.Pause(); + } + else + { + _progressBarStoryboard.Resume(); + } + }, System.Windows.Threading.DispatcherPriority.Render); + + } + }; _settings.PropertyChanged += (o, e) => { From 4f12b80603fabea748fa1c0baf2f08d901656995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 19 Jan 2021 22:46:18 +0800 Subject: [PATCH 269/442] fix testing --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 09c7d9a30..e7f96fc87 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -24,7 +24,7 @@ namespace Flow.Launcher.Test.Plugins return new List(); } - private List MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString) + private List MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token) { return new List { From 89443bace76f44fb0551f3f63d70a8779868b92a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 20 Jan 2021 06:34:26 +1100 Subject: [PATCH 270/442] fix formatting --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 1 - Flow.Launcher/ViewModel/ResultViewModel.cs | 4 ---- Flow.Launcher/ViewModel/ResultsForUpdate.cs | 1 - Flow.Launcher/ViewModel/ResultsViewModel.cs | 15 --------------- 4 files changed, 21 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 73f565a33..ac333d567 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -39,7 +39,6 @@ namespace Flow.Launcher.Infrastructure.Image var usage = LoadStorageToConcurrentDictionary(); - foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index fa0c51a65..c91bbb107 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,14 +1,11 @@ using System; -using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Microsoft.FSharp.Core; namespace Flow.Launcher.ViewModel { @@ -128,7 +125,6 @@ namespace Flow.Launcher.ViewModel } } - public override int GetHashCode() { return Result.GetHashCode(); diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs index 2257d35b0..be48f53c1 100644 --- a/Flow.Launcher/ViewModel/ResultsForUpdate.cs +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -23,7 +23,6 @@ namespace Flow.Launcher.ViewModel Token = token; } - public ResultsForUpdate(List results, PluginMetadata metadata, Query query, CancellationToken token) { Results = results; diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 932d6fc4b..1b8dd602d 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.ComponentModel; -using System.Configuration; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,7 +9,6 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; -using System.Windows.Forms; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -137,13 +134,11 @@ namespace Flow.Launcher.ViewModel Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList()); } - /// /// To avoid deadlock, this method should not called from main thread /// public void AddResults(List newRawResults, string resultId) { - lock (_collectionLock) { var newResults = NewResults(newRawResults, resultId); @@ -193,8 +188,6 @@ namespace Flow.Launcher.ViewModel Results.Update(newResults, token); if (Results.Any()) SelectedItem = Results[0]; - - } switch (Visbility) @@ -212,7 +205,6 @@ namespace Flow.Launcher.ViewModel } - private List NewResults(List newRawResults, string resultId) { if (newRawResults.Count == 0) @@ -222,8 +214,6 @@ namespace Flow.Launcher.ViewModel var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); - - return results.Where(r => r.Result.PluginID != resultId) .Concat(results.Intersect(newResults).Union(newResults)) .OrderByDescending(r => r.Result.Score) @@ -245,7 +235,6 @@ namespace Flow.Launcher.ViewModel } #endregion - #region FormattedText Dependency Property public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached( "FormattedText", @@ -279,7 +268,6 @@ namespace Flow.Launcher.ViewModel public class ResultCollection : ObservableCollection { - private long editTime = 0; private bool _suppressNotifying = false; @@ -323,9 +311,6 @@ namespace Flow.Launcher.ViewModel ClearItems(); } - - - /// /// Update the results collection with new results, try to keep identical results /// From cd92512fe5b1cd6ff58b691077283164f82613c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 20 Jan 2021 13:47:25 +0800 Subject: [PATCH 271/442] Optimize code --- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 2 +- .../Search/SearchManager.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index efdb5a6fe..d15069981 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -28,7 +28,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo RecurseSubdirectories = true }, query, search, criteria, token); - return DirectorySearch(null, query, search, criteria, token); // null will be passed as default + return DirectorySearch(new EnumerationOptions(), query, search, criteria, token); // null will be passed as default } public string ConstructSearchCriteria(string search) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index d51833a6f..6c9b81c88 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -80,12 +80,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (token.IsCancellationRequested) return null; - results.AddRange(await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, + var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, DirectoryInfoClassSearch, useIndexSearch, query, locationPath, - token).ConfigureAwait(false)); + token).ConfigureAwait(false); + + if (token.IsCancellationRequested) + return null; + + results.AddRange(directoryResult); return results; } From 1aa119d672af4ee1c13250902a8936bd5c02bccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 20 Jan 2021 13:49:50 +0800 Subject: [PATCH 272/442] fix some legacy code from #195 --- Flow.Launcher/PublicAPIInstance.cs | 1 + Flow.Launcher/ViewModel/ResultsViewModel.cs | 49 +++++---------------- 2 files changed, 12 insertions(+), 38 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 17673a62a..89e4fab3b 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -92,6 +92,7 @@ namespace Flow.Launcher { Application.Current.Dispatcher.Invoke(() => { + useMainWindowAsOwner = false; var msg = useMainWindowAsOwner ? new Msg { Owner = Application.Current.MainWindow } : new Msg(); msg.Show(title, subTitle, iconPath); }); diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 1b8dd602d..f04304267 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -139,39 +139,9 @@ namespace Flow.Launcher.ViewModel /// public void AddResults(List newRawResults, string resultId) { - lock (_collectionLock) - { - var newResults = NewResults(newRawResults, resultId); + var newResults = NewResults(newRawResults, resultId); - // https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf - // fix selected index flow - var updateTask = Task.Run(() => - { - // update UI in one run, so it can avoid UI flickering - - Results.Update(newResults); - if (Results.Any()) - SelectedItem = Results[0]; - }); - if (!updateTask.Wait(300)) - { - updateTask.Dispose(); - throw new TimeoutException("Update result use too much time."); - } - - } - - if (Visbility != Visibility.Visible && Results.Count > 0) - { - Margin = new Thickness { Top = 8 }; - SelectedIndex = 0; - Visbility = Visibility.Visible; - } - else - { - Margin = new Thickness { Top = 0 }; - Visbility = Visibility.Collapsed; - } + UpdateResults(newResults); } /// /// To avoid deadlock, this method should not called from main thread @@ -181,10 +151,15 @@ namespace Flow.Launcher.ViewModel var newResults = NewResults(resultsForUpdates); if (token.IsCancellationRequested) return; + UpdateResults(newResults, token); + + } + + private void UpdateResults(List newResults, CancellationToken token = default) + { lock (_collectionLock) { // update UI in one run, so it can avoid UI flickering - Results.Update(newResults, token); if (Results.Any()) SelectedItem = Results[0]; @@ -202,7 +177,6 @@ namespace Flow.Launcher.ViewModel Visbility = Visibility.Collapsed; break; } - } private List NewResults(List newRawResults, string resultId) @@ -212,10 +186,10 @@ namespace Flow.Launcher.ViewModel var results = Results as IEnumerable; - var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList(); + var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)); return results.Where(r => r.Result.PluginID != resultId) - .Concat(results.Intersect(newResults).Union(newResults)) + .Concat(newResults) .OrderByDescending(r => r.Result.Score) .ToList(); } @@ -228,8 +202,7 @@ namespace Flow.Launcher.ViewModel var results = Results as IEnumerable; return results.Where(r => r != null && !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID)) - .Concat( - resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings))) + .Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings))) .OrderByDescending(rv => rv.Result.Score) .ToList(); } From a42c9c0ac17ea8af242853883129fd5584534543 Mon Sep 17 00:00:00 2001 From: adamjijo Date: Wed, 20 Jan 2021 11:26:24 +0530 Subject: [PATCH 273/442] Added themes. Added Nord and Nord Darker themes. --- Flow.Launcher/Themes/Nord Darker.xaml | 56 +++++++++++++++++++++++++++ Flow.Launcher/Themes/Nord.xaml | 56 +++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 Flow.Launcher/Themes/Nord Darker.xaml create mode 100644 Flow.Launcher/Themes/Nord.xaml diff --git a/Flow.Launcher/Themes/Nord Darker.xaml b/Flow.Launcher/Themes/Nord Darker.xaml new file mode 100644 index 000000000..13f4bf230 --- /dev/null +++ b/Flow.Launcher/Themes/Nord Darker.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + #5e81ac + + + + diff --git a/Flow.Launcher/Themes/Nord.xaml b/Flow.Launcher/Themes/Nord.xaml new file mode 100644 index 000000000..2253b3410 --- /dev/null +++ b/Flow.Launcher/Themes/Nord.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + #5e81ac + + + + From 9d126df225222f41f6e23d9aa4b0601ce470f920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 20 Jan 2021 17:49:39 +0800 Subject: [PATCH 274/442] Use List replace ObservableCollection to have control toward Capacity --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 53 +++++++++++---------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index f04304267..55dea7440 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -239,49 +239,50 @@ namespace Flow.Launcher.ViewModel } #endregion - public class ResultCollection : ObservableCollection + public class ResultCollection : List, INotifyCollectionChanged { private long editTime = 0; - private bool _suppressNotifying = false; - private CancellationToken _token; - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - if (!_suppressNotifying) - { - base.OnCollectionChanged(e); - } + CollectionChanged(this, e); } - public void BulkAddRange(IEnumerable resultViews) + public void BulkAddAll(List resultViews) { - // suppress notifying before adding all element - _suppressNotifying = true; - foreach (var item in resultViews) - { - Add(item); - } - _suppressNotifying = false; - // manually update event - // wpf use directx / double buffered already, so just reset all won't cause ui flickering + AddRange(resultViews); + + // can return because the list will be cleared next time updated, which include a reset event if (_token.IsCancellationRequested) return; + + // manually update event + // wpf use directx / double buffered already, so just reset all won't cause ui flickering OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - public void AddRange(IEnumerable Items) + private void AddAll(List Items) { foreach (var item in Items) { if (_token.IsCancellationRequested) return; Add(item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } } - public void RemoveAll() + public void RemoveAll(int Capacity = 512) { - ClearItems(); + Clear(); + if (this.Capacity > 8000) + { + this.Capacity = Capacity; + + } + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// @@ -296,15 +297,19 @@ namespace Flow.Launcher.ViewModel if (editTime < 10 || newItems.Count < 30) { - if (Count != 0) ClearItems(); - AddRange(newItems); + if (Count != 0) RemoveAll(newItems.Count); + AddAll(newItems); editTime++; return; } else { Clear(); - BulkAddRange(newItems); + BulkAddAll(newItems); + if (Capacity > 8000 && newItems.Count < 3000) + { + Capacity = newItems.Count; + } editTime++; } } From 912cca6c5f62df9c6fcca505f94b2c1300a4905b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 20 Jan 2021 18:00:16 +0800 Subject: [PATCH 275/442] Use callback to check whether downloading manifest successfully. --- .../Main.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 932248de4..c8680371c 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -30,7 +30,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return new PluginsManagerSettings(viewModel); } - public async Task InitAsync(PluginInitContext context) + public Task InitAsync(PluginInitContext context) { Context = context; viewModel = new SettingsViewModel(context); @@ -38,16 +38,19 @@ namespace Flow.Launcher.Plugin.PluginsManager contextMenu = new ContextMenu(Context); pluginManager = new PluginsManager(Context, Settings); var updateManifestTask = pluginManager.UpdateManifest(); - if (await Task.WhenAny(updateManifestTask, Task.Delay(1000)) == updateManifestTask) + _ = updateManifestTask.ContinueWith(t => { - lastUpdateTime = DateTime.Now; - } - else - { - context.API.ShowMsg("Plugin Manifest Download Fail.", - "Please check if you can connect to github.com. "+ + if (t.IsCompletedSuccessfully) + lastUpdateTime = DateTime.Now; + else + { + context.API.ShowMsg("Plugin Manifest Download Fail.", + "Please check if you can connect to github.com. " + "This error means you may not be able to Install and Update Plugin.", pluginManager.icoPath, false); - } + } + }); + + return Task.CompletedTask; } public List LoadContextMenus(Result selectedResult) From 2ca2041ed7938f1d0de8cac5efa6ea501d17b014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 20 Jan 2021 19:07:12 +0800 Subject: [PATCH 276/442] Increase opacity for blurblack and blurwhite --- Flow.Launcher/Themes/BlurBlack.xaml | 4 ++-- Flow.Launcher/Themes/BlurWhite.xaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/Themes/BlurBlack.xaml b/Flow.Launcher/Themes/BlurBlack.xaml index dc80c144b..5c615d500 100644 --- a/Flow.Launcher/Themes/BlurBlack.xaml +++ b/Flow.Launcher/Themes/BlurBlack.xaml @@ -19,7 +19,7 @@ @@ -28,7 +28,7 @@ - + diff --git a/Flow.Launcher/Themes/BlurWhite.xaml b/Flow.Launcher/Themes/BlurWhite.xaml index 1c1f2f9ec..6a130bb39 100644 --- a/Flow.Launcher/Themes/BlurWhite.xaml +++ b/Flow.Launcher/Themes/BlurWhite.xaml @@ -17,7 +17,7 @@ @@ -26,7 +26,7 @@ - + From 258de0109e9ff4567bff5a147762265e69d9b526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Wed, 20 Jan 2021 19:07:38 +0800 Subject: [PATCH 277/442] Allign Gray and Light design with Darker --- Flow.Launcher/Themes/Gray.xaml | 20 +++++++++----------- Flow.Launcher/Themes/Light.xaml | 12 ++++++------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher/Themes/Gray.xaml b/Flow.Launcher/Themes/Gray.xaml index 16a1db274..1fbaa959a 100644 --- a/Flow.Launcher/Themes/Gray.xaml +++ b/Flow.Launcher/Themes/Gray.xaml @@ -4,21 +4,19 @@ @@ -31,15 +29,15 @@ - #00AAF6 + #787878 @@ -38,7 +38,7 @@ - #3875D7 + #909090 + + + + + + + + + + + + + + + #356ef3 + + + + + + diff --git a/Flow.Launcher/Themes/BlurBlack.xaml b/Flow.Launcher/Themes/BlurBlack.xaml index 5c615d500..dc80c144b 100644 --- a/Flow.Launcher/Themes/BlurBlack.xaml +++ b/Flow.Launcher/Themes/BlurBlack.xaml @@ -19,7 +19,7 @@ @@ -28,7 +28,7 @@ - + From 4e7c9a76e8b8ec1d90f9229a9fd8c21719f2d99c Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 21 Jan 2021 13:38:26 +1100 Subject: [PATCH 279/442] version bump PluginsManager --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 7f3f4a5cf..75d6038d4 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.6.0", + "Version": "1.6.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From 49d5faee9eca3df4018dbb1b8b845771e860d7c9 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 21 Jan 2021 13:41:50 +1100 Subject: [PATCH 280/442] formatting --- Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index c8680371c..66bfd2ab5 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -41,7 +41,9 @@ namespace Flow.Launcher.Plugin.PluginsManager _ = updateManifestTask.ContinueWith(t => { if (t.IsCompletedSuccessfully) + { lastUpdateTime = DateTime.Now; + } else { context.API.ShowMsg("Plugin Manifest Download Fail.", From 5d1790cb0eda18afb6de232ce8cfd198f2905c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 21 Jan 2021 12:11:01 +0800 Subject: [PATCH 281/442] change visibility from ProgressBar.Visibility to _viewModel.ProgressBarVisibility --- Flow.Launcher/MainWindow.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 114b6cd7f..96efda94d 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -94,7 +94,7 @@ namespace Flow.Launcher { Dispatcher.Invoke(() => { - if (ProgressBar.Visibility == Visibility.Hidden) + if (_viewModel.ProgressBarVisibility == Visibility.Hidden) { _progressBarStoryboard.Pause(); } From e0c345ae130be3010c4627aed3f133004f44fcd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 21 Jan 2021 12:18:23 +0800 Subject: [PATCH 282/442] removing legacy code for testing --- Flow.Launcher/PublicAPIInstance.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 89e4fab3b..17673a62a 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -92,7 +92,6 @@ namespace Flow.Launcher { Application.Current.Dispatcher.Invoke(() => { - useMainWindowAsOwner = false; var msg = useMainWindowAsOwner ? new Msg { Owner = Application.Current.MainWindow } : new Msg(); msg.Show(title, subTitle, iconPath); }); From 8311b39ddc034f73ac11e2c7185067cbaebe00f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 21 Jan 2021 18:35:24 +0800 Subject: [PATCH 283/442] Add null check for OnCollectionChanged --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 55dea7440..2a0818b7d 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -249,7 +249,7 @@ namespace Flow.Launcher.ViewModel protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - CollectionChanged(this, e); + CollectionChanged?.Invoke(this, e); } public void BulkAddAll(List resultViews) From 5389763f5801c989b68d7ec1df7f1a826ebc3389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 21 Jan 2021 18:38:02 +0800 Subject: [PATCH 284/442] Add another check to avoid some corner case --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 2a0818b7d..4a4a45f11 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -247,6 +247,7 @@ namespace Flow.Launcher.ViewModel public event NotifyCollectionChangedEventHandler CollectionChanged; + protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); @@ -277,7 +278,7 @@ namespace Flow.Launcher.ViewModel public void RemoveAll(int Capacity = 512) { Clear(); - if (this.Capacity > 8000) + if (this.Capacity > 8000 && Capacity < this.Capacity) { this.Capacity = Capacity; From 1a758c391922b70259538848d9a646e195c2a396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 21 Jan 2021 19:39:18 +0800 Subject: [PATCH 285/442] Use Token.throwifCancellationRequested --- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 3 +-- .../Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index d15069981..779827b6d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -76,8 +76,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo fileList.Add(resultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false)); } - if (token.IsCancellationRequested) - return null; + token.ThrowIfCancellationRequested(); } } catch (Exception e) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 6c9b81c88..7e3bf7776 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -77,8 +77,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch)); - if (token.IsCancellationRequested) - return null; + token.ThrowIfCancellationRequested(); var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, DirectoryInfoClassSearch, @@ -87,11 +86,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search locationPath, token).ConfigureAwait(false); - if (token.IsCancellationRequested) - return null; + token.ThrowIfCancellationRequested(); results.AddRange(directoryResult); - + return results; } From f388b75d2610c92e1128edfba55667a80e53f6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Thu, 21 Jan 2021 19:51:22 +0800 Subject: [PATCH 286/442] Add index when calling NotifyCollectionChangeAction.Add --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 4a4a45f11..4808e0ee0 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -267,12 +267,13 @@ namespace Flow.Launcher.ViewModel } private void AddAll(List Items) { - foreach (var item in Items) + for (int i = 0; i < Items.Count; i++) { + var item = Items[i]; if (_token.IsCancellationRequested) return; Add(item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i)); } } public void RemoveAll(int Capacity = 512) From b426dd10d1b1b6b60d4c40b85fbdc8444675d708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 22 Jan 2021 16:19:03 +0800 Subject: [PATCH 287/442] Rewrite LocationPathString match --- .../SharedCommands/FilesFolders.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 27cd1a558..a185b2a1c 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Text.RegularExpressions; using System.Windows; namespace Flow.Launcher.Plugin.SharedCommands @@ -142,35 +143,28 @@ namespace Flow.Launcher.Plugin.SharedCommands Process.Start(FileExplorerProgramEXE, $" /select,\"{path}\""); } + /// /// This checks whether a given string is a directory path or network location string. /// It does not check if location actually exists. /// public static bool IsLocationPathString(string querySearchString) { - if (string.IsNullOrEmpty(querySearchString)) + if (string.IsNullOrEmpty(querySearchString) || querySearchString.Length < 3) return false; // // shared folder location, and not \\\location\ - if (querySearchString.Length >= 3 - && querySearchString.StartsWith(@"\\") - && char.IsLetter(querySearchString[2])) + if (querySearchString.StartsWith(@"\\") + && querySearchString[2] != '\\') return true; // c:\ - if (querySearchString.Length == 3 - && char.IsLetter(querySearchString[0]) + if (char.IsLetter(querySearchString[0]) && querySearchString[1] == ':' && querySearchString[2] == '\\') - return true; - - // c:\\ - if (querySearchString.Length >= 4 - && char.IsLetter(querySearchString[0]) - && querySearchString[1] == ':' - && querySearchString[2] == '\\' - && char.IsLetter(querySearchString[3])) - return true; + { + return querySearchString.Length == 3 || querySearchString[3] != '\\'; + } return false; } From 3effb401b7c0124ea9628e1a2c7d7d6b22343d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 22 Jan 2021 16:22:52 +0800 Subject: [PATCH 288/442] make it become an extension method --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 2 +- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index a185b2a1c..98beba987 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -148,7 +148,7 @@ namespace Flow.Launcher.Plugin.SharedCommands /// This checks whether a given string is a directory path or network location string. /// It does not check if location actually exists. /// - public static bool IsLocationPathString(string querySearchString) + public static bool IsLocationPathString(this string querySearchString) { if (string.IsNullOrEmpty(querySearchString) || querySearchString.Length < 3) return false; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 6b3a96912..452d16105 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -58,7 +58,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search // Query is a location path with a full environment variable, eg. %appdata%\somefolder\ var isEnvironmentVariablePath = querySearch[1..].Contains("%\\"); - if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath) + if (!querySearch.IsLocationPathString() && !isEnvironmentVariablePath) { results.AddRange(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false)); @@ -70,7 +70,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (isEnvironmentVariablePath) locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath); - if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) + if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).IsLocationPathString()) return results; var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); From 2db3f829e48331337fcf5c27878cda96282144a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Fri, 22 Jan 2021 17:13:35 +0800 Subject: [PATCH 289/442] change logic 1. Disable animation when progressbar is hidden or mainwindow is collapsed 2. resume only when both visibility and progressbar visibility is visible --- Flow.Launcher/MainWindow.xaml.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 96efda94d..77fec72a1 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -1,4 +1,5 @@ -using System; + +using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; @@ -85,9 +86,14 @@ namespace Flow.Launcher _viewModel.LastQuerySelected = true; } + if (_viewModel.ProgressBarVisibility == Visibility.Visible) + { + _progressBarStoryboard.Resume(); + } } else { + _progressBarStoryboard.Pause(); } } else if (e.PropertyName == nameof(MainViewModel.ProgressBarVisibility)) @@ -98,7 +104,7 @@ namespace Flow.Launcher { _progressBarStoryboard.Pause(); } - else + else if (Visibility == Visibility.Visible) { _progressBarStoryboard.Resume(); } From bc0146e68bda6785bce50f425ce7454068662b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 23 Jan 2021 10:53:01 +0800 Subject: [PATCH 290/442] Use Window Search Orderby instead of getting the result and order them by filename --- .../Search/WindowsIndex/IndexSearch.cs | 9 +++---- .../Search/WindowsIndex/QueryConstructor.cs | 24 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs index 5b1d47ef8..f162eacbe 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs @@ -24,9 +24,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex internal async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token) { - var folderResults = new List(); - var fileResults = new List(); var results = new List(); + var fileResults = new List(); try { @@ -55,7 +54,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex if (dataReaderResults.GetString(2) == "Directory") { - folderResults.Add(resultManager.CreateFolderResult( + results.Add(resultManager.CreateFolderResult( dataReaderResults.GetString(0), path, path, @@ -83,8 +82,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex LogException("General error from performing index search", e); } + results.AddRange(fileResults); + // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. - return results.Concat(folderResults.OrderBy(x => x.Title)).Concat(fileResults.OrderBy(x => x.Title)).ToList(); ; + return results; } internal async Task> WindowsIndexSearchAsync(string searchString, string connectionString, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 5718fdb0a..e844801e9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -42,7 +42,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex // Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer var queryHelper = catalogManager.GetQueryHelper(); - + return queryHelper; } @@ -81,11 +81,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex var previousLevelDirectory = path.Substring(0, indexOfSeparator); if (string.IsNullOrEmpty(itemName)) - return searchDepth + $"{previousLevelDirectory}'"; + return $"{searchDepth}{previousLevelDirectory}'{QueryOrderByFileNameRestriction}"; - return $"(System.FileName LIKE '{itemName}%' " + - $"OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND " + - searchDepth + $"{previousLevelDirectory}'"; + return $"(System.FileName LIKE '{itemName}%' OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND {searchDepth}{previousLevelDirectory}' {QueryOrderByFileNameRestriction}"; } /// @@ -98,7 +96,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex if (path.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > path.LastIndexOf(Constants.DirectorySeperator)) return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path); - return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path); + return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path) + QueryOrderByFileNameRestriction; } /// @@ -107,16 +105,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex public string QueryForAllFilesAndFolders(string userSearchString) { // Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause - return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch(); + return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch + + QueryOrderByFileNameRestriction; } /// /// Set the required WHERE clause restriction to search for all files and folders. /// - public string QueryWhereRestrictionsForAllFilesAndFoldersSearch() - { - return $"scope='file:'"; - } + public const string QueryWhereRestrictionsForAllFilesAndFoldersSearch = "scope='file:'"; + + public const string QueryOrderByFileNameRestriction = " ORDER BY System.FileName"; + /// /// Search will be performed on all indexed file contents for the specified search keywords. @@ -125,7 +124,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; - return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch(); + return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch + + QueryOrderByFileNameRestriction; } /// From 5285c46bc18b1f35d530ef42f75fe16fcc4e6387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 23 Jan 2021 10:59:37 +0800 Subject: [PATCH 291/442] fix testing --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 09c7d9a30..28cc4b3a6 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -116,11 +116,8 @@ namespace Flow.Launcher.Test.Plugins [TestCase("scope='file:'")] public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString) { - // Given - var queryConstructor = new QueryConstructor(new Settings()); - //When - var resultString = queryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch(); + var resultString = QueryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch; // Then Assert.IsTrue(resultString == expectedString, From 0fe92d35cbe3dd01c73507c69820b671b24d3438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 23 Jan 2021 11:32:41 +0800 Subject: [PATCH 292/442] fix testing and a potential error coding --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 16 ++++++++-------- .../Search/WindowsIndex/QueryConstructor.cs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 28cc4b3a6..d0e58c9e0 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -60,8 +60,8 @@ namespace Flow.Launcher.Test.Plugins $"Actual: {result}{Environment.NewLine}"); } - [TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")] - [TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")] + [TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")] + [TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")] public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) { // Given @@ -79,7 +79,7 @@ namespace Flow.Launcher.Test.Plugins [TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " + "FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" + - " AND directory='file:C:\\SomeFolder'")] + " AND directory='file:C:\\SomeFolder' ORDER BY System.FileName")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { @@ -127,7 +127,7 @@ namespace Flow.Launcher.Test.Plugins [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " + "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + - "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")] + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")] public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { @@ -202,7 +202,7 @@ namespace Flow.Launcher.Test.Plugins } [TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " + - "FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:'")] + "FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")] public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { @@ -292,9 +292,9 @@ namespace Flow.Launcher.Test.Plugins } [TestCase("c:\\SomeFolder\\>", "scope='file:c:\\SomeFolder'")] - [TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + - "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " + - "scope='file:c:\\SomeFolder'")] + [TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + + "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " + + "scope='file:c:\\SomeFolder'")] public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString) { // Given diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index e844801e9..20e85bbb5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -81,9 +81,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex var previousLevelDirectory = path.Substring(0, indexOfSeparator); if (string.IsNullOrEmpty(itemName)) - return $"{searchDepth}{previousLevelDirectory}'{QueryOrderByFileNameRestriction}"; + return $"{searchDepth}{previousLevelDirectory}'"; - return $"(System.FileName LIKE '{itemName}%' OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND {searchDepth}{previousLevelDirectory}' {QueryOrderByFileNameRestriction}"; + return $"(System.FileName LIKE '{itemName}%' OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND {searchDepth}{previousLevelDirectory}'"; } /// @@ -94,7 +94,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; if (path.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > path.LastIndexOf(Constants.DirectorySeperator)) - return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path); + return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path) + QueryOrderByFileNameRestriction; return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path) + QueryOrderByFileNameRestriction; } From e74a0c99b68160be83c4e897195c8f6daaeae6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sat, 23 Jan 2021 12:05:23 +0800 Subject: [PATCH 293/442] fix most untranslated string in Setting window --- Flow.Launcher/CustomQueryHotkeySetting.xaml | 19 ++++++++++--------- Flow.Launcher/Languages/en.xaml | 3 +++ Flow.Launcher/SettingWindow.xaml | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index 5f4cdff19..bf6a35dff 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -5,7 +5,7 @@ Icon="Images\app.png" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" - Title="Custom Plugin Hotkey" Height="200" Width="674.766"> + Title="{DynamicResource customeQueryHotkeyTitle}" Height="200" Width="674.766"> @@ -19,22 +19,23 @@ - + + - - + + - - + + public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt) { - if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult(false, UserSettingSearchPrecision); + if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) + return new MatchResult(false, UserSettingSearchPrecision); query = query.Trim(); - - stringToCompare = _alphabet?.Translate(stringToCompare) ?? stringToCompare; - - // This also can be done by spliting the query - - //(var spaceSplit, var upperSplit) = stringToCompare switch - //{ - // string s when s.Contains(' ') => (s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(w => w.First()), - // default(IEnumerable)), - // string s when s.Any(c => char.IsUpper(c)) && s.Any(c => char.IsLower(c)) => - // (null, Regex.Split(s, @"(? w.First())), - // _ => ((IEnumerable)null, (IEnumerable)null) - //}; + TranslationMapping map; + (stringToCompare, map) = _alphabet?.Translate(stringToCompare) ?? (stringToCompare, null); var currentQueryIndex = 0; var acronymMatchData = new List(); @@ -72,28 +62,24 @@ namespace Flow.Launcher.Infrastructure if (currentQueryIndex >= queryWithoutCase.Length) break; - if (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex])) - { - acronymMatchData.Add(compareIndex); - currentQueryIndex++; - continue; - } switch (stringToCompare[compareIndex]) { - case char c when compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex]) - || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) - || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]) - || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): - acronymMatchData.Add(compareIndex); + case var c when (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == + char.ToLower(stringToCompare[compareIndex])) + || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) + || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == + queryWithoutCase[currentQueryIndex]) + || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): + acronymMatchData.Add(map?.MapToOriginalIndex(compareIndex) ?? compareIndex); currentQueryIndex++; continue; - case char c when char.IsWhiteSpace(c): + case var c when char.IsWhiteSpace(c): compareIndex++; acronymScore -= 10; break; - case char c when char.IsUpper(c) || char.IsNumber(c): + case var c when char.IsUpper(c) || char.IsNumber(c): acronymScore -= 10; break; } @@ -105,7 +91,7 @@ namespace Flow.Launcher.Infrastructure var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; - var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var querySubstrings = queryWithoutCase.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries); int currentQuerySubstringIndex = 0; var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; var currentQuerySubstringCharacterIndex = 0; @@ -120,9 +106,10 @@ namespace Flow.Launcher.Infrastructure var indexList = new List(); List spaceIndices = new List(); - for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) + for (var compareStringIndex = 0; + compareStringIndex < fullStringToCompareWithoutCase.Length; + compareStringIndex++) { - // To maintain a list of indices which correspond to spaces in the string to compare // To populate the list only for the first query substring if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0) @@ -130,7 +117,8 @@ namespace Flow.Launcher.Infrastructure spaceIndices.Add(compareStringIndex); } - if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex]) + if (fullStringToCompareWithoutCase[compareStringIndex] != + currentQuerySubstring[currentQuerySubstringCharacterIndex]) { matchFoundInPreviousLoop = false; continue; @@ -154,14 +142,16 @@ namespace Flow.Launcher.Infrastructure // 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)) + 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); + indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, + firstMatchIndexInWord, indexList); } } @@ -174,11 +164,13 @@ namespace Flow.Launcher.Infrastructure if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length) { // if any of the substrings was not matched then consider as all are not matched - allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString; + allSubstringsContainedInCompareString = + matchFoundInPreviousLoop && allSubstringsContainedInCompareString; currentQuerySubstringIndex++; - allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length); + allQuerySubstringsMatched = + AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length); if (allQuerySubstringsMatched) break; @@ -188,13 +180,16 @@ namespace Flow.Launcher.Infrastructure } } + // proceed to calculate score if every char or substring without whitespaces matched if (allQuerySubstringsMatched) { var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex); - var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); + var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, + lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); - return new MatchResult(true, UserSettingSearchPrecision, indexList, score); + var resultList = indexList.Distinct().Select(x => map?.MapToOriginalIndex(x) ?? x).ToList(); + return new MatchResult(true, UserSettingSearchPrecision, resultList, score); } return new MatchResult(false, UserSettingSearchPrecision); @@ -209,14 +204,15 @@ namespace Flow.Launcher.Infrastructure } else { - int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault(); + int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)) + .FirstOrDefault(item => firstMatchIndex > item); int closestSpaceIndex = ind ?? -1; return closestSpaceIndex; } } private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex, - string fullStringToCompareWithoutCase, string currentQuerySubstring) + string fullStringToCompareWithoutCase, string currentQuerySubstring) { var allMatch = true; for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++) @@ -231,7 +227,8 @@ namespace Flow.Launcher.Infrastructure return allMatch; } - private static List GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List indexList) + private static List GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, + int firstMatchIndexInWord, List indexList) { var updatedList = new List(); @@ -252,7 +249,8 @@ namespace Flow.Launcher.Infrastructure return currentQuerySubstringIndex >= querySubstringsLength; } - private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString) + 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, @@ -347,7 +345,7 @@ namespace Flow.Launcher.Infrastructure private bool IsSearchPrecisionScoreMet(int rawScore) { - return rawScore >= (int)SearchPrecision; + return rawScore >= (int) SearchPrecision; } private int ScoreAfterSearchPrecisionFilter(int rawScore) @@ -360,4 +358,4 @@ namespace Flow.Launcher.Infrastructure { public bool IgnoreCase { get; set; } = true; } -} +} \ No newline at end of file From 1e016d7aab47f2959ce9d6e3787fa35ea0aca5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Tue, 22 Dec 2020 22:58:27 +0800 Subject: [PATCH 315/442] optimize use --- Flow.Launcher.Infrastructure/StringMatcher.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index e885798b7..22334c4bd 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -71,7 +71,7 @@ namespace Flow.Launcher.Infrastructure || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex]) || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): - acronymMatchData.Add(map?.MapToOriginalIndex(compareIndex) ?? compareIndex); + acronymMatchData.Add(compareIndex); currentQueryIndex++; continue; @@ -86,7 +86,10 @@ namespace Flow.Launcher.Infrastructure } if (acronymMatchData.Count == query.Length && acronymScore >= 60) + { + acronymMatchData = acronymMatchData.Select(x => map?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); + } var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; @@ -188,7 +191,7 @@ namespace Flow.Launcher.Infrastructure var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); - var resultList = indexList.Distinct().Select(x => map?.MapToOriginalIndex(x) ?? x).ToList(); + var resultList = indexList.Select(x => map?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); return new MatchResult(true, UserSettingSearchPrecision, resultList, score); } From 9aa4802542c5e066ed8378fa3f2af6e191336055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sat, 26 Dec 2020 00:29:35 +0800 Subject: [PATCH 316/442] Use Binary Search instead of Linear search to reduce time complexity. Add Key Property for debugging. --- .../PinyinAlphabet.cs | 78 +++++++++++++++---- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs index 4f1aedd4a..be3c58f66 100644 --- a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs +++ b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs @@ -18,6 +18,13 @@ namespace Flow.Launcher.Infrastructure private List translatedIndexs = new List(); private int translaedLength = 0; + public string key { get; private set; } + + public void setKey(string key) + { + this.key = key; + } + public void AddNewIndex(int originalIndex, int translatedIndex, int length) { if (constructed) @@ -29,28 +36,64 @@ namespace Flow.Launcher.Infrastructure translaedLength += length - 1; } - public int? MapToOriginalIndex(int translatedIndex) + public int MapToOriginalIndex(int translatedIndex) { if (translatedIndex > translatedIndexs.Last()) return translatedIndex - translaedLength - 1; - - for (var i = 0; i < originalIndexs.Count; i++) - { - if (translatedIndex >= translatedIndexs[i * 2] && translatedIndex < translatedIndexs[i * 2 + 1]) - return originalIndexs[i]; - if (translatedIndex < translatedIndexs[i * 2]) - { - int indexDiff = 0; - for (int j = 0; j < i; j++) - { - indexDiff += translatedIndexs[i * 2 + 1] - translatedIndexs[i * 2] - 1; - } - return translatedIndex - indexDiff; + int lowerBound = 0; + int upperBound = originalIndexs.Count - 1; + + int count = 0; + + + // Corner case handle + if (translatedIndex < translatedIndexs[0]) + return translatedIndex; + if (translatedIndex > translatedIndexs.Last()) + { + int indexDef = 0; + for (int k = 0; k < originalIndexs.Count; k++) + { + indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2]; } + + return translatedIndex - indexDef - 1; } - return translatedIndex; + // Binary Search with Range + for (int i = originalIndexs.Count / 2;; count++) + { + if (translatedIndex < translatedIndexs[i * 2]) + { + // move to lower middle + upperBound = i; + i = (i + lowerBound) / 2; + } + else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1) + { + lowerBound = i; + // move to upper middle + // due to floor of integer division, move one up on corner case + i = (i + upperBound + 1) / 2; + } + else + return originalIndexs[i]; + + if (upperBound - lowerBound <= 1 && + translatedIndex > translatedIndexs[lowerBound * 2 + 1] && + translatedIndex < translatedIndexs[upperBound * 2]) + { + int indexDef = 0; + + for (int j = 0; j < upperBound; j++) + { + indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2]; + } + + return translatedIndex - indexDef - 1; + } + } } public void endConstruct() @@ -117,7 +160,10 @@ namespace Flow.Launcher.Infrastructure map.endConstruct(); - return _pinyinCache[content] = (resultBuilder.ToString(), map); + var key = resultBuilder.ToString(); + map.setKey(key); + + return _pinyinCache[content] = (key, map); } else { From 213059996af5edcabce739773d903a874ecfb186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Sun, 27 Dec 2020 20:16:20 +0800 Subject: [PATCH 317/442] Use inner loop to evaluate acronym match (Big Change) Don't end loop before acronym match end since if acronym match exist, we will use that one. --- Flow.Launcher.Infrastructure/StringMatcher.cs | 99 ++++++++++++------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 22334c4bd..7ade76cdf 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -51,46 +51,13 @@ namespace Flow.Launcher.Infrastructure TranslationMapping map; (stringToCompare, map) = _alphabet?.Translate(stringToCompare) ?? (stringToCompare, null); - var currentQueryIndex = 0; + var currentAcronymQueryIndex = 0; var acronymMatchData = new List(); var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; + // preset acronymScore int acronymScore = 100; - for (int compareIndex = 0; compareIndex < stringToCompare.Length; compareIndex++) - { - if (currentQueryIndex >= queryWithoutCase.Length) - break; - - - switch (stringToCompare[compareIndex]) - { - case var c when (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == - char.ToLower(stringToCompare[compareIndex])) - || (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex]) - || (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == - queryWithoutCase[currentQueryIndex]) - || (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]): - acronymMatchData.Add(compareIndex); - currentQueryIndex++; - continue; - - case var c when char.IsWhiteSpace(c): - compareIndex++; - acronymScore -= 10; - break; - case var c when char.IsUpper(c) || char.IsNumber(c): - acronymScore -= 10; - break; - } - } - - if (acronymMatchData.Count == query.Length && acronymScore >= 60) - { - acronymMatchData = acronymMatchData.Select(x => map?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); - return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); - } - var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; @@ -109,24 +76,72 @@ namespace Flow.Launcher.Infrastructure var indexList = new List(); List spaceIndices = new List(); + bool spaceMet = false; + for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) { + if (currentAcronymQueryIndex >= queryWithoutCase.Length + || allQuerySubstringsMatched && acronymScore < (int) UserSettingSearchPrecision) + break; + + // To maintain a list of indices which correspond to spaces in the string to compare // To populate the list only for the first query substring - if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0) + if (fullStringToCompareWithoutCase[compareStringIndex] == ' ' && currentQuerySubstringIndex == 0) { spaceIndices.Add(compareStringIndex); } - if (fullStringToCompareWithoutCase[compareStringIndex] != + // Acronym check + if (char.IsUpper(stringToCompare[compareStringIndex]) || + char.IsNumber(stringToCompare[compareStringIndex]) || + char.IsWhiteSpace(stringToCompare[compareStringIndex]) || + spaceMet) + { + if (fullStringToCompareWithoutCase[compareStringIndex] == + queryWithoutCase[currentAcronymQueryIndex]) + { + currentAcronymQueryIndex++; + + if (!spaceMet) + { + char currentCompareChar = stringToCompare[compareStringIndex]; + spaceMet = char.IsWhiteSpace(currentCompareChar); + // if is space, no need to check whether upper or digit, though insignificant + if (!spaceMet && compareStringIndex == 0 || char.IsUpper(currentCompareChar) || + char.IsDigit(currentCompareChar)) + { + acronymMatchData.Add(compareStringIndex); + } + } + else if (!(spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex]))) + { + acronymMatchData.Add(compareStringIndex); + } + } + else + { + spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex]); + // Acronym Penalty + if (!spaceMet) + { + acronymScore -= 10; + } + } + } + // Acronym end + + if (allQuerySubstringsMatched || fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex]) { matchFoundInPreviousLoop = false; + continue; } + if (firstMatchIndex < 0) { // first matched char will become the start of the compared string @@ -174,8 +189,9 @@ namespace Flow.Launcher.Infrastructure allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length); + if (allQuerySubstringsMatched) - break; + continue; // otherwise move to the next query substring currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; @@ -183,6 +199,12 @@ namespace Flow.Launcher.Infrastructure } } + // return acronym Match if possible + if (acronymMatchData.Count == query.Length && acronymScore >= (int) UserSettingSearchPrecision) + { + acronymMatchData = acronymMatchData.Select(x => map?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); + return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); + } // proceed to calculate score if every char or substring without whitespaces matched if (allQuerySubstringsMatched) @@ -249,6 +271,7 @@ namespace Flow.Launcher.Infrastructure private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength) { + // Acronym won't utilize the substring to match return currentQuerySubstringIndex >= querySubstringsLength; } From 1cd21c0ccb36827ca01eb57384b44710d5064561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 25 Jan 2021 06:04:05 +0800 Subject: [PATCH 318/442] Fix testing --- Flow.Launcher.Test/FuzzyMatcherTest.cs | 97 ++++++++++++++++++-------- 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 468b94457..8925ae708 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -40,7 +40,7 @@ namespace Flow.Launcher.Test Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)) .Cast() .ToList() - .ForEach(x => listToReturn.Add((int)x)); + .ForEach(x => listToReturn.Add((int) x)); return listToReturn; } @@ -92,7 +92,8 @@ namespace Flow.Launcher.Test [TestCase("cand")] [TestCase("cpywa")] [TestCase("ccs")] - public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm) + public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults( + string searchTerm) { var results = new List(); var matcher = new StringMatcher(); @@ -108,9 +109,9 @@ namespace Flow.Launcher.Test foreach (var precisionScore in GetPrecisionScores()) { var filteredResult = results.Where(result => result.Score >= precisionScore) - .Select(result => result) - .OrderByDescending(x => x.Score) - .ToList(); + .Select(result => result) + .OrderByDescending(x => x.Score) + .ToList(); Debug.WriteLine(""); Debug.WriteLine("###############################################"); @@ -119,6 +120,7 @@ namespace Flow.Launcher.Test { Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title); } + Debug.WriteLine("###############################################"); Debug.WriteLine(""); @@ -128,11 +130,11 @@ namespace Flow.Launcher.Test [TestCase(Chrome, Chrome, 157)] [TestCase(Chrome, LastIsChrome, 147)] - [TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 25)] + [TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 90)] [TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)] [TestCase(Chrome, CandyCrushSagaFromKing, 0)] - [TestCase("sql", MicrosoftSqlServerManagementStudio, 110)] - [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)]//double spacing intended + [TestCase("sql", MicrosoftSqlServerManagementStudio, 90)] + [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)] //double spacing intended public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring( string queryString, string compareString, int expectedScore) { @@ -141,20 +143,20 @@ namespace Flow.Launcher.Test var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore; // Should - Assert.AreEqual(expectedScore, rawScore, + Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}"); } [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)] + [TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, 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 WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( string queryString, string compareString, @@ -170,7 +172,8 @@ namespace Flow.Launcher.Test Debug.WriteLine(""); Debug.WriteLine("###############################################"); Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); - Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})"); + Debug.WriteLine( + $"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})"); Debug.WriteLine("###############################################"); Debug.WriteLine(""); @@ -179,13 +182,15 @@ namespace Flow.Launcher.Test $"Query:{queryString}{Environment.NewLine} " + $"Compare:{compareString}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + - $"Precision Score: {(int)expectedPrecisionScore}"); + $"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 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)] @@ -195,15 +200,21 @@ namespace Flow.Launcher.Test [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("mssms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] + [TestCase("msms", 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("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)] [TestCase("cod", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] [TestCase("code", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] [TestCase("codes", "Visual Studio Codes", StringMatcher.SearchPrecisionScore.Regular, true)] + [TestCase("vsc", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] + [TestCase("vs", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] + [TestCase("vc", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings( string queryString, string compareString, @@ -211,7 +222,7 @@ namespace Flow.Launcher.Test bool expectedPrecisionResult) { // When - var matcher = new StringMatcher { UserSettingSearchPrecision = expectedPrecisionScore }; + var matcher = new StringMatcher {UserSettingSearchPrecision = expectedPrecisionScore}; // Given var matchResult = matcher.FuzzyMatch(queryString, compareString); @@ -219,7 +230,8 @@ namespace Flow.Launcher.Test Debug.WriteLine(""); Debug.WriteLine("###############################################"); Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); - Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})"); + Debug.WriteLine( + $"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})"); Debug.WriteLine("###############################################"); Debug.WriteLine(""); @@ -228,7 +240,7 @@ namespace Flow.Launcher.Test $"Query:{queryString}{Environment.NewLine} " + $"Compare:{compareString}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + - $"Precision Score: {(int)expectedPrecisionScore}"); + $"Precision Score: {(int) expectedPrecisionScore}"); } [TestCase("man", "Task Manager", "eManual")] @@ -238,7 +250,7 @@ namespace Flow.Launcher.Test string queryString, string compareString1, string compareString2) { // When - var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular }; + var matcher = new StringMatcher {UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular}; // Given var compareString1Result = matcher.FuzzyMatch(queryString, compareString1); @@ -247,8 +259,10 @@ namespace Flow.Launcher.Test Debug.WriteLine(""); Debug.WriteLine("###############################################"); Debug.WriteLine($"QueryString: \"{queryString}\"{Environment.NewLine}"); - Debug.WriteLine($"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}"); - Debug.WriteLine($"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}"); + Debug.WriteLine( + $"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}"); + Debug.WriteLine( + $"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}"); Debug.WriteLine("###############################################"); Debug.WriteLine(""); @@ -256,13 +270,13 @@ namespace Flow.Launcher.Test Assert.True(compareString1Result.Score > compareString2Result.Score, $"Query: \"{queryString}\"{Environment.NewLine} " + $"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" + - $"Should be greater than{ Environment.NewLine}" + + $"Should be greater than{Environment.NewLine}" + $"CompareString2: \"{compareString2}\", Score: {compareString1Result.Score}{Environment.NewLine}"); } [TestCase("vim", "Vim", "ignoreDescription", "ignore.exe", "Vim Diff", "ignoreDescription", "ignore.exe")] public void WhenMultipleResults_ExactMatchingResult_ShouldHaveGreatestScore( - string queryString, string firstName, string firstDescription, string firstExecutableName, + string queryString, string firstName, string firstDescription, string firstExecutableName, string secondName, string secondDescription, string secondExecutableName) { // Act @@ -275,15 +289,36 @@ namespace Flow.Launcher.Test var secondDescriptionMatch = matcher.FuzzyMatch(queryString, secondDescription).RawScore; var secondExecutableNameMatch = matcher.FuzzyMatch(queryString, secondExecutableName).RawScore; - var firstScore = new[] { firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch }.Max(); - var secondScore = new[] { secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch }.Max(); + var firstScore = new[] {firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch}.Max(); + var secondScore = new[] {secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch}.Max(); // Assert Assert.IsTrue(firstScore > secondScore, $"Query: \"{queryString}\"{Environment.NewLine} " + $"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" + - $"Should be greater than{ Environment.NewLine}" + + $"Should be greater than{Environment.NewLine}" + $"Name of second: \"{secondName}\", Final Score: {secondScore}{Environment.NewLine}"); } + + [TestCase("vsc","Visual Studio Code", 100)] + [TestCase("jbr","JetBrain Rider",100)] + [TestCase("jr","JetBrain Rider",90)] + [TestCase("vs","Visual Studio",100)] + [TestCase("vs","Visual Studio Preview",100)] + [TestCase("vsp","Visual Studio Preview",100)] + [TestCase("vsp","Visual Studio",0)] + [TestCase("pc","Postman Canary",100)] + + public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString, + int desiredScore) + { + var matcher = new StringMatcher(); + var score = matcher.FuzzyMatch(queryString, compareString).Score; + Assert.IsTrue(score == desiredScore, + $@"Query: ""{queryString}"" + CompareString: ""{compareString}"" + Score: {score} + Desired Score: {desiredScore}"); + } } } \ No newline at end of file From ff5e3695e9e538ceafc997b96adf2a4f7cb0a4f2 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 Jan 2021 13:50:41 +1100 Subject: [PATCH 319/442] add return if no quick access links or query --- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index fd8e254f8..cac08a6bd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -45,6 +45,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search && string.IsNullOrEmpty(query.Search)) return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context); + // No records in QuickFolderAccessLinks, user has not typed any query apart from SearchActionKeyword, no need for further search + if (string.IsNullOrEmpty(query.Search)) + return results; + var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context); if (quickFolderLinks.Count > 0) From 9914124d200ef5988c0488f0f65a9e4bc3082d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 25 Jan 2021 11:00:56 +0800 Subject: [PATCH 320/442] Remove extra checking --- .../Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index cac08a6bd..d7840af5c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -40,14 +40,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false); // This allows the user to type the assigned action keyword and only see the list of quick folder links - if (settings.QuickFolderAccessLinks.Count > 0 - && query.ActionKeyword == settings.SearchActionKeyword - && string.IsNullOrEmpty(query.Search)) - return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context); - - // No records in QuickFolderAccessLinks, user has not typed any query apart from SearchActionKeyword, no need for further search if (string.IsNullOrEmpty(query.Search)) - return results; + return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context); var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context); From 8a56cc6cd8b86e291e158908ec7976784fe8564a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 25 Jan 2021 11:06:03 +0800 Subject: [PATCH 321/442] Use singleton in QuickFolderAccess.cs --- .../Search/FolderLinks/QuickFolderAccess.cs | 26 ++++++++++++------- .../Search/SearchManager.cs | 7 ++--- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs index 8bd19956e..e9cf7ce80 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs @@ -6,25 +6,31 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks { public class QuickFolderAccess { - internal List FolderListMatched(Query query, List folderLinks, PluginInitContext context) + private readonly ResultManager _resultManager; + + public QuickFolderAccess(PluginInitContext context) + { + _resultManager = new ResultManager(context); + } + + internal List FolderListMatched(Query query, List folderLinks) { if (string.IsNullOrEmpty(query.Search)) return new List(); string search = query.Search.ToLower(); - - var queriedFolderLinks = folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase)); + + var queriedFolderLinks = + folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase)); return queriedFolderLinks.Select(item => - new ResultManager(context) - .CreateFolderResult(item.Nickname, item.Path, item.Path, query)) - .ToList(); + _resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query)) + .ToList(); } - internal List FolderListAll(Query query, List folderLinks, PluginInitContext context) + internal List FolderListAll(Query query, List folderLinks) => folderLinks - .Select(item => - new ResultManager(context).CreateFolderResult(item.Nickname, item.Path, item.Path, query)) + .Select(item => _resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query)) .ToList(); } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index d7840af5c..14aefeb19 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -16,7 +16,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search private readonly IndexSearch indexSearch; - private readonly QuickFolderAccess quickFolderAccess = new QuickFolderAccess(); + private readonly QuickFolderAccess quickFolderAccess; private readonly ResultManager resultManager; @@ -28,6 +28,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search indexSearch = new IndexSearch(context); resultManager = new ResultManager(context); this.settings = settings; + quickFolderAccess = new QuickFolderAccess(context); } internal async Task> SearchAsync(Query query, CancellationToken token) @@ -41,9 +42,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search // This allows the user to type the assigned action keyword and only see the list of quick folder links if (string.IsNullOrEmpty(query.Search)) - return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context); + return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks); - var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context); + var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks); if (quickFolderLinks.Count > 0) results.AddRange(quickFolderLinks); From 35782e430884f04b5c84c0458d7a49f856df6532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 25 Jan 2021 11:17:28 +0800 Subject: [PATCH 322/442] Version Bump --- Plugins/Flow.Launcher.Plugin.Explorer/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index 76fd36bb5..1e92d2254 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -7,7 +7,7 @@ "Name": "Explorer", "Description": "Search and manage files and folders. Explorer utilises Windows Index Search", "Author": "Jeremy Wu", - "Version": "1.4.1", + "Version": "1.5.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", From 8dc5def2e921138cfb125cc230cef9f0d91473e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BC=98=E9=9F=AC?= Date: Mon, 25 Jan 2021 11:19:59 +0800 Subject: [PATCH 323/442] Remove extra whitespace --- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 14aefeb19..64fa7b780 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -170,4 +170,4 @@ namespace Flow.Launcher.Plugin.Explorer.Search return indexSearch.PathIsIndexed(pathToDirectory); } } -} +} \ No newline at end of file From e46feb1165ac1f8e717336833b22b8c2f5f3ff4f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 Jan 2021 18:57:58 +1100 Subject: [PATCH 324/442] fix formatting --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 2 -- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 3 ++- .../Search/FolderLinks/QuickFolderAccess.cs | 2 +- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 4808e0ee0..4afb9a241 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -280,10 +280,8 @@ namespace Flow.Launcher.ViewModel { Clear(); if (this.Capacity > 8000 && Capacity < this.Capacity) - { this.Capacity = Capacity; - } OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 779827b6d..5124f6fb3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -81,7 +81,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo } catch (Exception e) { - if (!(e is ArgumentException)) throw e; + if (!(e is ArgumentException)) + throw e; results.Add(new Result {Title = e.Message, Score = 501}); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs index e9cf7ce80..ccaf87ef4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs @@ -33,4 +33,4 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks .Select(item => _resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query)) .ToList(); } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 64fa7b780..14aefeb19 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -170,4 +170,4 @@ namespace Flow.Launcher.Plugin.Explorer.Search return indexSearch.PathIsIndexed(pathToDirectory); } } -} \ No newline at end of file +} From 163bfa303baedc480cb68a1f2ce84a89a3d9d1cf Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 Jan 2021 19:18:13 +1100 Subject: [PATCH 325/442] formatting and naming --- Flow.Launcher/ViewModel/ResultsViewModel.cs | 3 ++- .../Search/FolderLinks/QuickFolderAccess.cs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 4afb9a241..feab3a751 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -149,10 +149,11 @@ namespace Flow.Launcher.ViewModel public void AddResults(IEnumerable resultsForUpdates, CancellationToken token) { var newResults = NewResults(resultsForUpdates); + if (token.IsCancellationRequested) return; - UpdateResults(newResults, token); + UpdateResults(newResults, token); } private void UpdateResults(List newResults, CancellationToken token = default) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs index ccaf87ef4..6f0020ac9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs @@ -6,11 +6,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks { public class QuickFolderAccess { - private readonly ResultManager _resultManager; + private readonly ResultManager resultManager; public QuickFolderAccess(PluginInitContext context) { - _resultManager = new ResultManager(context); + resultManager = new ResultManager(context); } internal List FolderListMatched(Query query, List folderLinks) @@ -24,13 +24,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase)); return queriedFolderLinks.Select(item => - _resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query)) + resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query)) .ToList(); } internal List FolderListAll(Query query, List folderLinks) => folderLinks - .Select(item => _resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query)) + .Select(item => resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query)) .ToList(); } } From 5d894ec154dca7949cb0d9cb8c67c22d9e667519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 25 Jan 2021 17:18:48 +0800 Subject: [PATCH 326/442] Don't await download manifest when it is unavaliable, but only await when calling install method to allow user using uninstall when manifest is not ready. --- .../Flow.Launcher.Plugin.PluginsManager/Main.cs | 15 +++++++++++++-- .../PluginsManager.cs | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 66bfd2ab5..7836f1441 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -69,8 +69,19 @@ namespace Flow.Launcher.Plugin.PluginsManager if ((DateTime.Now - lastUpdateTime).TotalHours > 12) // 12 hours { - await pluginManager.UpdateManifest(); - lastUpdateTime = DateTime.Now; + _ = pluginManager.UpdateManifest().ContinueWith(t => + { + if (t.IsCompletedSuccessfully) + { + lastUpdateTime = DateTime.Now; + } + else + { + Context.API.ShowMsg("Plugin Manifest Download Fail.", + "Please check if you can connect to github.com. " + + "This error means you may not be able to Install and Update Plugin.", pluginManager.icoPath, false); + } + }); } return search switch diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 0f5e6d9e8..e25d97b36 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -46,9 +46,9 @@ namespace Flow.Launcher.Plugin.PluginsManager Settings = settings; } - internal async Task UpdateManifest() + internal Task UpdateManifest() { - await pluginsManifest.DownloadManifest(); + return _downloadManifestTask = pluginsManifest.DownloadManifest(); } internal List GetDefaultHotKeys() From fd32d4884e689d24ee51d0e02d577985520164a4 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 Jan 2021 15:31:21 +1100 Subject: [PATCH 327/442] revert unintended CustomQueryHotkeySetting ui change --- Flow.Launcher/CustomQueryHotkeySetting.xaml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index bf6a35dff..a97f90733 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -19,23 +19,22 @@ - - + - - + + - - + + public abstract string SupportedLanguage { get; set; } - protected abstract Task ExecuteQueryAsync(Query query, CancellationToken token); + protected abstract Task ExecuteQueryAsync(Query query, CancellationToken token); protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest); protected abstract string ExecuteContextMenu(Result selectedResult); @@ -45,53 +46,66 @@ namespace Flow.Launcher.Core.Plugin } } - private List DeserializedResult(string output) + + + private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) { - if (!String.IsNullOrEmpty(output)) + var results = new List(); + if (queryResponseModel.Result == null) return null; + + foreach (JsonRPCResult result in queryResponseModel.Result) { - List results = new List(); - - JsonRPCQueryResponseModel queryResponseModel = - JsonSerializer.Deserialize(output); - if (queryResponseModel.Result == null) return null; - - foreach (JsonRPCResult result in queryResponseModel.Result) + result.Action = c => { - result.Action = c => - { - if (result.JsonRPCAction == null) return false; + if (result.JsonRPCAction == null) return false; - if (!String.IsNullOrEmpty(result.JsonRPCAction.Method)) + if (!string.IsNullOrEmpty(result.JsonRPCAction.Method)) + { + if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher.")) { - if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher.")) + ExecuteFlowLauncherAPI(result.JsonRPCAction.Method.Substring(4), + result.JsonRPCAction.Parameters); + } + else + { + string actionReponse = ExecuteCallback(result.JsonRPCAction); + JsonRPCRequestModel jsonRpcRequestModel = + JsonSerializer.Deserialize(actionReponse); + if (jsonRpcRequestModel != null + && !string.IsNullOrEmpty(jsonRpcRequestModel.Method) + && jsonRpcRequestModel.Method.StartsWith("Flow.Launcher.")) { - ExecuteFlowLauncherAPI(result.JsonRPCAction.Method.Substring(4), - result.JsonRPCAction.Parameters); - } - else - { - string actionReponse = ExecuteCallback(result.JsonRPCAction); - JsonRPCRequestModel jsonRpcRequestModel = - JsonSerializer.Deserialize(actionReponse); - if (jsonRpcRequestModel != null - && !String.IsNullOrEmpty(jsonRpcRequestModel.Method) - && jsonRpcRequestModel.Method.StartsWith("Flow.Launcher.")) - { - ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method.Substring(4), - jsonRpcRequestModel.Parameters); - } + ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method.Substring(4), + jsonRpcRequestModel.Parameters); } } + } - return !result.JsonRPCAction.DontHideAfterAction; - }; - results.Add(result); - } - - return results; + return !result.JsonRPCAction.DontHideAfterAction; + }; + results.Add(result); } - return null; + return results; + } + + private async Task> DeserializedResultAsync(Stream output) + { + if (output == Stream.Null) return null; + + JsonRPCQueryResponseModel queryResponseModel = await + JsonSerializer.DeserializeAsync(output); + + return ParseResults(queryResponseModel); + } + + private List DeserializedResult(string output) + { + if (string.IsNullOrEmpty(output)) return null; + + JsonRPCQueryResponseModel queryResponseModel = + JsonSerializer.Deserialize(output); + return ParseResults(queryResponseModel); } private void ExecuteFlowLauncherAPI(string method, object[] parameters) @@ -119,15 +133,17 @@ namespace Flow.Launcher.Core.Plugin /// /// Cancellation Token /// - protected Task ExecuteAsync(string fileName, string arguments, CancellationToken token = default) + protected Task ExecuteAsync(string fileName, string arguments, CancellationToken token = default) { - ProcessStartInfo start = new ProcessStartInfo(); - start.FileName = fileName; - start.Arguments = arguments; - start.UseShellExecute = false; - start.CreateNoWindow = true; - start.RedirectStandardOutput = true; - start.RedirectStandardError = true; + ProcessStartInfo start = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; return ExecuteAsync(start, token); } @@ -137,7 +153,7 @@ namespace Flow.Launcher.Core.Plugin { using var process = Process.Start(startInfo); if (process == null) return string.Empty; - + using var standardOutput = process.StandardOutput; var result = standardOutput.ReadToEnd(); @@ -157,12 +173,12 @@ namespace Flow.Launcher.Core.Plugin if (result.StartsWith("DEBUG:")) { - MessageBox.Show(new Form {TopMost = true}, result.Substring(6)); + MessageBox.Show(new Form { TopMost = true }, result.Substring(6)); return string.Empty; } return result; - + } catch (Exception e) { @@ -173,7 +189,7 @@ namespace Flow.Launcher.Core.Plugin } } - protected async Task ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default) + protected async Task ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default) { try { @@ -181,17 +197,14 @@ namespace Flow.Launcher.Core.Plugin if (process == null) { Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process"); - return string.Empty; + return Stream.Null; } - using var standardOutput = process.StandardOutput; + var result = process.StandardOutput.BaseStream; token.ThrowIfCancellationRequested(); - var result = await standardOutput.ReadToEndAsync(); - token.ThrowIfCancellationRequested(); - - if (string.IsNullOrEmpty(result)) + if (!process.StandardError.EndOfStream) { using var standardError = process.StandardError; var error = await standardError.ReadToEndAsync(); @@ -200,17 +213,11 @@ namespace Flow.Launcher.Core.Plugin if (!string.IsNullOrEmpty(error)) { Log.Error($"|JsonRPCPlugin.ExecuteAsync|{error}"); - return string.Empty; + return Stream.Null; } Log.Error("|JsonRPCPlugin.ExecuteAsync|Empty standard output and standard error."); - return string.Empty; - } - - if (result.StartsWith("DEBUG:")) - { - MessageBox.Show(new Form {TopMost = true}, result.Substring(6)); - return string.Empty; + return Stream.Null; } return result; @@ -220,16 +227,18 @@ namespace Flow.Launcher.Core.Plugin Log.Exception( $"|JsonRPCPlugin.ExecuteAsync|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>", e); - return string.Empty; + return Stream.Null; } } + + public async Task> QueryAsync(Query query, CancellationToken token) { - string output = await ExecuteQueryAsync(query, token); + var output = await ExecuteQueryAsync(query, token); try { - return DeserializedResult(output); + return await DeserializedResultAsync(output); } catch (Exception e) { diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs index 750684954..356a68a81 100644 --- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs +++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs @@ -30,7 +30,7 @@ namespace Flow.Launcher.Core.Plugin } - protected override Task ExecuteQueryAsync(Query query, CancellationToken token) + protected override Task ExecuteQueryAsync(Query query, CancellationToken token) { JsonRPCServerRequestModel request = new JsonRPCServerRequestModel { From 824ea07ab2ca46c6563563708371f1af2d2a7f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 14 Feb 2021 13:59:59 +0800 Subject: [PATCH 415/442] add Debug message field for debugging --- Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 2 + Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 43 ++++++++++++---------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index a0731f8fb..b1c8ff6ea 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -45,6 +45,8 @@ namespace Flow.Launcher.Core.Plugin { [JsonPropertyName("result")] public new List Result { get; set; } + + public string DebugMessage { get; set; } } public class JsonRPCRequestModel : JsonRPCModelBase diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index a5633b118..e342a5f94 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -48,11 +48,35 @@ namespace Flow.Launcher.Core.Plugin + private async Task> DeserializedResultAsync(Stream output) + { + if (output == Stream.Null) return null; + + JsonRPCQueryResponseModel queryResponseModel = await + JsonSerializer.DeserializeAsync(output); + + return ParseResults(queryResponseModel); + } + + private List DeserializedResult(string output) + { + if (string.IsNullOrEmpty(output)) return null; + + JsonRPCQueryResponseModel queryResponseModel = + JsonSerializer.Deserialize(output); + return ParseResults(queryResponseModel); + } + private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) { var results = new List(); if (queryResponseModel.Result == null) return null; + if(!string.IsNullOrEmpty(queryResponseModel.DebugMessage)) + { + context.API.ShowMsg(queryResponseModel.DebugMessage); + } + foreach (JsonRPCResult result in queryResponseModel.Result) { result.Action = c => @@ -89,25 +113,6 @@ namespace Flow.Launcher.Core.Plugin return results; } - private async Task> DeserializedResultAsync(Stream output) - { - if (output == Stream.Null) return null; - - JsonRPCQueryResponseModel queryResponseModel = await - JsonSerializer.DeserializeAsync(output); - - return ParseResults(queryResponseModel); - } - - private List DeserializedResult(string output) - { - if (string.IsNullOrEmpty(output)) return null; - - JsonRPCQueryResponseModel queryResponseModel = - JsonSerializer.Deserialize(output); - return ParseResults(queryResponseModel); - } - private void ExecuteFlowLauncherAPI(string method, object[] parameters) { MethodInfo methodInfo = PluginManager.API.GetType().GetMethod(method); From f9b8216e3b9e9021e5ea0eaee39bea0eae3837d1 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 14 Feb 2021 19:00:00 +1100 Subject: [PATCH 416/442] add copy bookmark's url to clipboard --- .../Images/copylink.png | Bin 0 -> 10725 bytes .../Languages/en.xaml | 2 + .../Main.cs | 47 ++++++++++++++++-- .../plugin.json | 2 +- 4 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png new file mode 100644 index 0000000000000000000000000000000000000000..0dee870b615bc9caddeff44be4096b4b5f6fa92e GIT binary patch literal 10725 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 tab Set browser from path: Choose + Copy url + Copy the bookmark's url to clipboard \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index 47493654f..3726f1683 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -1,6 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Windows; using System.Windows.Controls; +using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.BrowserBookmark.Commands; using Flow.Launcher.Plugin.BrowserBookmark.Models; @@ -9,7 +12,7 @@ using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.BrowserBookmark { - public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable + public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable, IContextMenu { private PluginInitContext context; @@ -60,7 +63,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark } return true; - } + }, + ContextData = new BookmarkAttributes { Url = c.Url } }).Where(r => r.Score > 0); return returnList.ToList(); } @@ -84,7 +88,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark } return true; - } + }, + ContextData = new BookmarkAttributes { Url = c.Url } }).ToList(); } } @@ -115,5 +120,39 @@ namespace Flow.Launcher.Plugin.BrowserBookmark { _storage.Save(); } + + public List LoadContextMenus(Result selectedResult) + { + return new List() { + new Result + { + Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), + SubTitle = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"), + Action = _ => + { + try + { + Clipboard.SetText(((BookmarkAttributes)selectedResult.ContextData).Url); + + return true; + } + catch (Exception e) + { + var message = "Failed to set url in clipboard"; + Log.Exception("Main",message, e, "LoadContextMenus"); + + context.API.ShowMsg(message); + + return false; + } + }, + IcoPath = "Images\\copylink.png" + }}; + } + + internal class BookmarkAttributes + { + internal string Url { get; set; } + } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json index b0c3d2e29..62311f514 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser Bookmarks", "Description": "Search your browser bookmarks", "Author": "qianlifeng, Ioannis G.", - "Version": "1.3.2", + "Version": "1.4.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", From 93f4b6d90adaf017a7e0df5a2dd6e8172a2686ef Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 14 Feb 2021 20:32:20 +1100 Subject: [PATCH 417/442] update to use SetDataObject --- Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index 3726f1683..b889bb0d0 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -132,7 +132,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark { try { - Clipboard.SetText(((BookmarkAttributes)selectedResult.ContextData).Url); + Clipboard.SetDataObject(((BookmarkAttributes)selectedResult.ContextData).Url); return true; } From f8557da336171ddb738715d700e70dca4bf2a5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 14 Feb 2021 18:08:30 +0800 Subject: [PATCH 418/442] add Logs, LoadJsonStorage, SaveJsonStorage to IPublicAPI --- .../Storage/JsonStorage.cs | 2 +- .../Storage/PluginJsonStorage.cs | 5 +++ Flow.Launcher.Plugin/IPublicAPI.cs | 12 ++++++ Flow.Launcher/PublicAPIInstance.cs | 39 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index f0e4a79fc..37cfec252 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -12,7 +12,7 @@ namespace Flow.Launcher.Infrastructure.Storage public class JsonStrorage where T : new() { private readonly JsonSerializerOptions _serializerSettings; - private T _data; + protected T _data; // need a new directory name public const string DirectoryName = "Settings"; public const string FileSuffix = ".json"; diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index 5418e1837..ca7c454c4 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -15,5 +15,10 @@ namespace Flow.Launcher.Infrastructure.Storage FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); } + + public PluginJsonStorage(T data) : this() + { + _data = data; + } } } diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index dd73eb0e5..2a69a2495 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -100,5 +101,16 @@ namespace Flow.Launcher.Plugin void RemoveActionKeyword(string pluginId, string oldActionKeyword); + void LogDebug(string className, string message, [CallerMemberName] string methodName = ""); + + void LogInfo(string className, string message, [CallerMemberName] string methodName = ""); + + void LogWarn(string className, string message, [CallerMemberName] string methodName = ""); + + void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = ""); + + T LoadJsonStorage(PluginMetadata metadata) where T : new(); + + void SaveJsonStorage(PluginMetadata metadata, T setting) where T : new(); } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 0afa67d1a..9c36a709e 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -18,6 +18,9 @@ using System.Threading; using System.IO; using Flow.Launcher.Infrastructure.Http; using JetBrains.Annotations; +using System.Runtime.CompilerServices; +using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.Storage; namespace Flow.Launcher { @@ -160,6 +163,41 @@ namespace Flow.Launcher { PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); } + + + public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") + { + Log.Debug(className, message, methodName); + } + + public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") + { + Log.Info(className, message, methodName); + } + + public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") + { + Log.Warn(className, message, methodName); + } + + public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") + { + Log.Exception(className, message, e, methodName); + } + + public T LoadJsonStorage(PluginMetadata metadata) where T : new() + { + var storage = new PluginJsonStorage(); + return storage.Load(); + } + + public void SaveJsonStorage(PluginMetadata metadata, T setting) where T : new() + { + var storage = new PluginJsonStorage(setting); + storage.Save(); + } + + #endregion #region Private Methods @@ -173,6 +211,7 @@ namespace Flow.Launcher return true; } + #endregion } } From 9fb44e6002d62227ffef1828c6e33c85a2ad6758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 14 Feb 2021 18:12:57 +0800 Subject: [PATCH 419/442] format code --- Flow.Launcher/PublicAPIInstance.cs | 97 ++++++------------------------ 1 file changed, 19 insertions(+), 78 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 9c36a709e..967cf1bf7 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -70,15 +70,9 @@ namespace Flow.Launcher UpdateManager.RestartApp(Constant.ApplicationFileName); } - public void RestarApp() - { - RestartApp(); - } + public void RestarApp() => RestartApp(); - public void CheckForNewUpdate() - { - _settingsVM.UpdateApp(); - } + public void CheckForNewUpdate() => _settingsVM.UpdateApp(); public void SaveAppAllSettings() { @@ -88,15 +82,9 @@ namespace Flow.Launcher ImageLoader.Save(); } - public Task ReloadAllPluginData() - { - return PluginManager.ReloadData(); - } + public Task ReloadAllPluginData() => PluginManager.ReloadData(); - public void ShowMsg(string title, string subTitle = "", string iconPath = "") - { - ShowMsg(title, subTitle, iconPath, true); - } + public void ShowMsg(string title, string subTitle = "", string iconPath = "") => ShowMsg(title, subTitle, iconPath, true); public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true) { @@ -115,87 +103,40 @@ namespace Flow.Launcher }); } - public void StartLoadingBar() - { - _mainVM.ProgressBarVisibility = Visibility.Visible; - } + public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible; - public void StopLoadingBar() - { - _mainVM.ProgressBarVisibility = Visibility.Collapsed; - } + public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed; - public string GetTranslation(string key) - { - return InternationalizationManager.Instance.GetTranslation(key); - } + public string GetTranslation(string key) => InternationalizationManager.Instance.GetTranslation(key); - public List GetAllPlugins() - { - return PluginManager.AllPlugins.ToList(); - } + public List GetAllPlugins() => PluginManager.AllPlugins.ToList(); public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); - public Task HttpGetStringAsync(string url, CancellationToken token = default) - { - return Http.GetAsync(url); - } + public Task HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url); - public Task HttpGetStreamAsync(string url, CancellationToken token = default) - { - return Http.GetStreamAsync(url); - } + public Task HttpGetStreamAsync(string url, CancellationToken token = default) => Http.GetStreamAsync(url); - public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath) - { - return Http.DownloadAsync(url, filePath); - } + public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath) => Http.DownloadAsync(url, filePath); - public void AddActionKeyword(string pluginId, string newActionKeyword) - { - PluginManager.AddActionKeyword(pluginId, newActionKeyword); - } + public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword); - public void RemoveActionKeyword(string pluginId, string oldActionKeyword) - { - PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); - } + public void RemoveActionKeyword(string pluginId, string oldActionKeyword) => PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); - public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") - { - Log.Debug(className, message, methodName); - } + public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") => Log.Debug(className, message, methodName); - public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") - { - Log.Info(className, message, methodName); - } + public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") => Log.Info(className, message, methodName); - public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") - { - Log.Warn(className, message, methodName); - } + public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") => Log.Warn(className, message, methodName); - public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") - { - Log.Exception(className, message, e, methodName); - } + public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName); - public T LoadJsonStorage(PluginMetadata metadata) where T : new() - { - var storage = new PluginJsonStorage(); - return storage.Load(); - } + public T LoadJsonStorage(PluginMetadata metadata) where T : new() => new PluginJsonStorage().Load(); - public void SaveJsonStorage(PluginMetadata metadata, T setting) where T : new() - { - var storage = new PluginJsonStorage(setting); - storage.Save(); - } + public void SaveJsonStorage(PluginMetadata metadata, T setting) where T : new() => new PluginJsonStorage(setting).Save(); #endregion From 71ca3bbf38b4fd55aca0540530d812d6df9d7011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 14 Feb 2021 18:15:46 +0800 Subject: [PATCH 420/442] formatting --- Flow.Launcher/PublicAPIInstance.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 967cf1bf7..327ee6658 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -111,8 +111,6 @@ namespace Flow.Launcher public List GetAllPlugins() => PluginManager.AllPlugins.ToList(); - public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; - public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); public Task HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url); @@ -125,7 +123,6 @@ namespace Flow.Launcher public void RemoveActionKeyword(string pluginId, string oldActionKeyword) => PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); - public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") => Log.Debug(className, message, methodName); public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") => Log.Info(className, message, methodName); @@ -138,6 +135,7 @@ namespace Flow.Launcher public void SaveJsonStorage(PluginMetadata metadata, T setting) where T : new() => new PluginJsonStorage(setting).Save(); + public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; #endregion From 8a26471c4fca63ce8b413d69ce3f32fd1849f389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 14 Feb 2021 18:25:09 +0800 Subject: [PATCH 421/442] Update Document and remove unused parameter --- Flow.Launcher.Plugin/IPublicAPI.cs | 61 +++++++++++++++++++++++++++++- Flow.Launcher/PublicAPIInstance.cs | 4 +- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index 2a69a2495..a3f711a17 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -89,28 +89,85 @@ namespace Flow.Launcher.Plugin /// event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; + /// + /// Fuzzy Search the string with query + /// + /// Query String + /// The string to Search for Query + /// Match results MatchResult FuzzySearch(string query, string stringToCompare); + /// + /// Http Get to the spefic URL + /// + /// URL to call Http Get + /// Cancellation Token + /// Task to get string result Task HttpGetStringAsync(string url, CancellationToken token = default); + /// + /// Http Get to the spefic URL + /// + /// URL to call Http Get + /// Cancellation Token + /// Task to get stream result Task HttpGetStreamAsync(string url, CancellationToken token = default); + /// + /// Download the specific url to a cretain file path + /// + /// URL to download file + /// place to store file + /// Task showing the progress Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath); + /// + /// Add ActionKeyword for specific plugin + /// + /// ID for plugin that needs to add action keyword + /// The actionkeyword that is supposed to be added void AddActionKeyword(string pluginId, string newActionKeyword); + /// + /// Remove ActionKeyword for specific plugin + /// + /// ID for plugin that needs to remove action keyword + /// The actionkeyword that is supposed to be removed void RemoveActionKeyword(string pluginId, string oldActionKeyword); + /// + /// Log Debug message + /// Message will only be logged in Debug mode + /// void LogDebug(string className, string message, [CallerMemberName] string methodName = ""); + /// + /// Log Message + /// void LogInfo(string className, string message, [CallerMemberName] string methodName = ""); + /// + /// Log Warning + /// void LogWarn(string className, string message, [CallerMemberName] string methodName = ""); + /// + /// Log an Exception + /// void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = ""); - T LoadJsonStorage(PluginMetadata metadata) where T : new(); + /// + /// Load JsonStorage for current plugin + /// + /// Type for deserialization + /// + T LoadJsonStorage() where T : new(); - void SaveJsonStorage(PluginMetadata metadata, T setting) where T : new(); + /// + /// Save JsonStorage for current plugin + /// + /// Type for Serialization + /// + void SaveJsonStorage(T setting) where T : new(); } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 327ee6658..200eb166a 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -131,9 +131,9 @@ namespace Flow.Launcher public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName); - public T LoadJsonStorage(PluginMetadata metadata) where T : new() => new PluginJsonStorage().Load(); + public T LoadJsonStorage() where T : new() => new PluginJsonStorage().Load(); - public void SaveJsonStorage(PluginMetadata metadata, T setting) where T : new() => new PluginJsonStorage(setting).Save(); + public void SaveJsonStorage(T setting) where T : new() => new PluginJsonStorage(setting).Save(); public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; From 1ec069bfcf1a982de6a2222bc70bcfe8eebfdf6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 14 Feb 2021 22:36:05 +0800 Subject: [PATCH 422/442] Use HttpCompletionOption.ResponseHeadersRead for Stream reading and downloading --- Flow.Launcher.Infrastructure/Http/Http.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index de2e82359..3a3e770a5 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -75,7 +75,7 @@ namespace Flow.Launcher.Infrastructure.Http { try { - using var response = await client.GetAsync(url, token); + using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); if (response.StatusCode == HttpStatusCode.OK) { await using var fileStream = new FileStream(filePath, FileMode.CreateNew); @@ -135,7 +135,7 @@ namespace Flow.Launcher.Infrastructure.Http public static async Task GetStreamAsync([NotNull] string url, CancellationToken token = default) { Log.Debug($"|Http.Get|Url <{url}>"); - var response = await client.GetAsync(url, token); + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); return await response.Content.ReadAsStreamAsync(); } } From 20c0b1788ffda2c5ac38589f828912cb5bc80137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Sun, 14 Feb 2021 22:43:11 +0800 Subject: [PATCH 423/442] Add Cancellation to IPublicAPI download method --- Flow.Launcher.Plugin/IPublicAPI.cs | 2 +- Flow.Launcher/PublicAPIInstance.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index a3f711a17..27a65191a 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -119,7 +119,7 @@ namespace Flow.Launcher.Plugin /// URL to download file /// place to store file /// Task showing the progress - Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath); + Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default); /// /// Add ActionKeyword for specific plugin diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 200eb166a..6c55e37fe 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -117,7 +117,7 @@ namespace Flow.Launcher public Task HttpGetStreamAsync(string url, CancellationToken token = default) => Http.GetStreamAsync(url); - public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath) => Http.DownloadAsync(url, filePath); + public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default) => Http.DownloadAsync(url, filePath, token); public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword); From 14e51c07310fd38d5e0a93964bf04a4c77ab9695 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 15 Feb 2021 06:42:18 +1100 Subject: [PATCH 424/442] update comments --- Flow.Launcher.Plugin/IPublicAPI.cs | 23 ++++++++++++----------- Flow.Launcher/PublicAPIInstance.cs | 1 - 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index 27a65191a..07e2b2700 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -90,15 +90,15 @@ namespace Flow.Launcher.Plugin event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; /// - /// Fuzzy Search the string with query + /// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses /// - /// Query String - /// The string to Search for Query + /// Query string + /// The string that will be compared against the query /// Match results MatchResult FuzzySearch(string query, string stringToCompare); /// - /// Http Get to the spefic URL + /// Http download the spefic url and return as string /// /// URL to call Http Get /// Cancellation Token @@ -106,7 +106,7 @@ namespace Flow.Launcher.Plugin Task HttpGetStringAsync(string url, CancellationToken token = default); /// - /// Http Get to the spefic URL + /// Http download the spefic url and return as stream /// /// URL to call Http Get /// Cancellation Token @@ -136,35 +136,36 @@ namespace Flow.Launcher.Plugin void RemoveActionKeyword(string pluginId, string oldActionKeyword); /// - /// Log Debug message + /// Log debug message /// Message will only be logged in Debug mode /// void LogDebug(string className, string message, [CallerMemberName] string methodName = ""); /// - /// Log Message + /// Log info message /// void LogInfo(string className, string message, [CallerMemberName] string methodName = ""); /// - /// Log Warning + /// Log warning message /// void LogWarn(string className, string message, [CallerMemberName] string methodName = ""); /// - /// Log an Exception + /// Log an Exception. Will throw if in debug mode so developer will be aware, + /// otherwise logs the eror message. This is the primary logging method used for Flow /// void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = ""); /// - /// Load JsonStorage for current plugin + /// Load JsonStorage for current plugin. This is the method used to load settings from json in Flow /// /// Type for deserialization /// T LoadJsonStorage() where T : new(); /// - /// Save JsonStorage for current plugin + /// Save JsonStorage for current plugin. This is the method used to save settings to json in Flow /// /// Type for Serialization /// diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 6c55e37fe..9b41c2c05 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -150,7 +150,6 @@ namespace Flow.Launcher return true; } - #endregion } } From 2b6b5e9d8bdae77b5ef8be2feb0abf47e5a7bf46 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 15 Feb 2021 07:05:46 +1100 Subject: [PATCH 425/442] version bump --- Plugins/Flow.Launcher.Plugin.Program/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index f713a33ec..93e5079c2 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "1.4.0", + "Version": "1.4.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", From ba5cb5788e61b8e6eefbef21fa12a32f40368fd0 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 15 Feb 2021 07:18:16 +1100 Subject: [PATCH 426/442] remove unused variables --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 1 - Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 22f4aea59..bd09cd9c6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -228,7 +228,6 @@ namespace Flow.Launcher.Plugin.Program public static void StartProcess(Func runProcess, ProcessStartInfo info) { - bool hide; try { runProcess(info); diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index d73483818..20df233bb 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -33,7 +33,6 @@ namespace Flow.Launcher.Plugin.Program.Programs public string Location => ParentDirectory; private const string ShortcutExtension = "lnk"; - private const string ApplicationReferenceExtension = "appref-ms"; private const string ExeExtension = "exe"; From 22bdbc93675df9485a8e4e9bad3f6c281d56a90c Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 15 Feb 2021 08:19:48 +1100 Subject: [PATCH 427/442] add match on path --- .../Programs/Win32.cs | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 20df233bb..3c0d1954c 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -41,36 +41,38 @@ namespace Flow.Launcher.Plugin.Program.Programs string title; MatchResult matchResult; + var nameMatchResult = StringMatcher.FuzzySearch(query, Name); + var descriptionMatchResult = StringMatcher.FuzzySearch(query, Description); + var pathMatchResult = StringMatcher.FuzzySearch(query, Path.GetFileNameWithoutExtension(FullPath)); + + var score = new[] { nameMatchResult.Score, descriptionMatchResult.Score, pathMatchResult.Score }.Max(); + + if (score < (int)StringMatcher.Instance.UserSettingSearchPrecision) + return null; + // We suppose Name won't be null - if (Description == null || Name.StartsWith(Description)) + if (score == nameMatchResult.Score && (Description == null || Name.StartsWith(Description))) { title = Name; - matchResult = StringMatcher.FuzzySearch(query, title); + matchResult = nameMatchResult; } - else if (Description.StartsWith(Name)) + else if (score == descriptionMatchResult.Score && Description.StartsWith(Name)) { title = Description; - matchResult = StringMatcher.FuzzySearch(query, Description); + + for (int i = 0; i < descriptionMatchResult.MatchData.Count; i++) + { + descriptionMatchResult.MatchData[i] += Name.Length + 2; // 2 is ": " + } + + matchResult = descriptionMatchResult; } else { title = $"{Name}: {Description}"; - var nameMatch = StringMatcher.FuzzySearch(query, Name); - var desciptionMatch = StringMatcher.FuzzySearch(query, Description); - if (desciptionMatch.Score > nameMatch.Score) - { - for (int i = 0; i < desciptionMatch.MatchData.Count; i++) - { - desciptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": " - } - matchResult = desciptionMatch; - } - else matchResult = nameMatch; + matchResult = pathMatchResult; } - if (!matchResult.Success) return null; - - var result = new Result { Title = title, From ad998b17886d0b4553eb53364ce86bec0172ce07 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 15 Feb 2021 14:01:07 +1100 Subject: [PATCH 428/442] add only path string match when lnk programs --- Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 3c0d1954c..74b85fd46 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -43,7 +43,10 @@ namespace Flow.Launcher.Plugin.Program.Programs var nameMatchResult = StringMatcher.FuzzySearch(query, Name); var descriptionMatchResult = StringMatcher.FuzzySearch(query, Description); - var pathMatchResult = StringMatcher.FuzzySearch(query, Path.GetFileNameWithoutExtension(FullPath)); + + var pathMatchResult = new MatchResult(false, 0, new List(), 0); + if ((LnkResolvedPath ?? FullPath) != FullPath) + pathMatchResult = StringMatcher.FuzzySearch(query, Path.GetFileNameWithoutExtension(FullPath)); var score = new[] { nameMatchResult.Score, descriptionMatchResult.Score, pathMatchResult.Score }.Max(); From 79d195f7e656dbf38fd5383fccf3cf0ecc97622e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 15 Feb 2021 11:49:18 +0800 Subject: [PATCH 429/442] Change logic --- .../Programs/Win32.cs | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 74b85fd46..275726595 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -39,41 +39,52 @@ namespace Flow.Launcher.Plugin.Program.Programs public Result Result(string query, IPublicAPI api) { string title; - MatchResult matchResult; var nameMatchResult = StringMatcher.FuzzySearch(query, Name); var descriptionMatchResult = StringMatcher.FuzzySearch(query, Description); var pathMatchResult = new MatchResult(false, 0, new List(), 0); - if ((LnkResolvedPath ?? FullPath) != FullPath) + if (ExecutableName != null) // only lnk program will need this one pathMatchResult = StringMatcher.FuzzySearch(query, Path.GetFileNameWithoutExtension(FullPath)); - var score = new[] { nameMatchResult.Score, descriptionMatchResult.Score, pathMatchResult.Score }.Max(); + MatchResult matchResult = nameMatchResult; - if (score < (int)StringMatcher.Instance.UserSettingSearchPrecision) - return null; + if (nameMatchResult.Score < descriptionMatchResult.Score) + matchResult = descriptionMatchResult; + + if (!matchResult.IsSearchPrecisionScoreMet()) + { + if (pathMatchResult.IsSearchPrecisionScoreMet()) + matchResult = pathMatchResult; + else return null; + } // We suppose Name won't be null - if (score == nameMatchResult.Score && (Description == null || Name.StartsWith(Description))) + if (Description == null || Name.StartsWith(Description)) { title = Name; - matchResult = nameMatchResult; } - else if (score == descriptionMatchResult.Score && Description.StartsWith(Name)) + else if (Description.StartsWith(Name)) { title = Description; - - for (int i = 0; i < descriptionMatchResult.MatchData.Count; i++) - { - descriptionMatchResult.MatchData[i] += Name.Length + 2; // 2 is ": " - } - - matchResult = descriptionMatchResult; } else { title = $"{Name}: {Description}"; - matchResult = pathMatchResult; + + if (matchResult == descriptionMatchResult) + { + for (int i = 0; i < descriptionMatchResult.MatchData.Count; i++) + { + matchResult.MatchData[i] += Name.Length + 2; // 2 is ": " + } + } + } + + if (matchResult == pathMatchResult) + { + // path Match won't have valid highlight data + matchResult.MatchData = new List(); } var result = new Result @@ -175,7 +186,7 @@ namespace Flow.Launcher.Plugin.Program.Programs public override string ToString() { - return ExecutableName; + return Name; } private static Win32 Win32Program(string path) @@ -200,7 +211,7 @@ namespace Flow.Launcher.Plugin.Program.Programs ProgramLogger.LogException($"|Win32|Win32Program|{path}" + $"|Permission denied when trying to load the program from {path}", e); - return new Win32() {Valid = false, Enabled = false}; + return new Win32() { Valid = false, Enabled = false }; } } @@ -278,7 +289,7 @@ namespace Flow.Launcher.Plugin.Program.Programs ProgramLogger.LogException($"|Win32|ExeProgram|{path}" + $"|Permission denied when trying to load the program from {path}", e); - return new Win32() {Valid = false, Enabled = false}; + return new Win32() { Valid = false, Enabled = false }; } } @@ -420,7 +431,6 @@ namespace Flow.Launcher.Plugin.Program.Programs return null; var entry = Win32Program(path); - entry.ExecutableName = Path.GetFileName(path); return entry; } @@ -450,7 +460,7 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - public static IEnumerable DistinctBy(IEnumerable source, Func selector) + public static IEnumerable DistinctBy(IEnumerable source, Func selector) { var set = new HashSet(); foreach (var item in source) From 208bde0f99539c2cc40ce6473e364db53196497f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 15 Feb 2021 11:50:27 +0800 Subject: [PATCH 430/442] Use ExecutableName instead of getting it from path --- Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 275726595..372d36524 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -45,7 +45,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var pathMatchResult = new MatchResult(false, 0, new List(), 0); if (ExecutableName != null) // only lnk program will need this one - pathMatchResult = StringMatcher.FuzzySearch(query, Path.GetFileNameWithoutExtension(FullPath)); + pathMatchResult = StringMatcher.FuzzySearch(query, ExecutableName); MatchResult matchResult = nameMatchResult; From 7e4ac1ce8f74f6809feedf4aa2fd0af6a084cad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 15 Feb 2021 11:58:33 +0800 Subject: [PATCH 431/442] fix formatting --- .../Programs/ApplicationActivationHelper.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs index 3023836c1..4b8423ed6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs @@ -21,9 +21,9 @@ namespace Flow.Launcher.Plugin.Program.Programs [ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IApplicationActivationManager { - IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId); - IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId); - IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId); + IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId); + IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId); + IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId); } // Application Activation Manager Class @@ -32,13 +32,13 @@ namespace Flow.Launcher.Plugin.Program.Programs { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/] - public extern IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId); + public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId); [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public extern IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId); + public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId); [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public extern IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId); + public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId); } } } From 4b4953e3ac78c17f94a9bc09f957d8880a33da0d Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 15 Feb 2021 20:02:18 +1100 Subject: [PATCH 432/442] version bump Program plugin --- Plugins/Flow.Launcher.Plugin.Program/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index 93e5079c2..d110124ff 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "1.4.1", + "Version": "1.4.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", From 575ff31ce56e056ac4f97f0cb5faa1c46d20f612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 15 Feb 2021 17:17:03 +0800 Subject: [PATCH 433/442] change format Initialize out variable inline, and use discard varable for unused variable --- .../Programs/UWP.cs | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 69afecb4f..855a11c86 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -56,10 +56,9 @@ namespace Flow.Launcher.Plugin.Program.Programs var namespaces = XmlNamespaces(path); InitPackageVersion(namespaces); - IStream stream; const uint noAttribute = 0x80; const Stgm exclusiveRead = Stgm.Read | Stgm.ShareExclusive; - var hResult = SHCreateStreamOnFileEx(path, exclusiveRead, noAttribute, false, null, out stream); + var hResult = SHCreateStreamOnFileEx(path, exclusiveRead, noAttribute, false, null, out IStream stream); if (hResult == Hresult.Ok) { @@ -345,14 +344,13 @@ namespace Flow.Launcher.Plugin.Program.Programs private async void Launch(IPublicAPI api) { var appManager = new ApplicationActivationHelper.ApplicationActivationManager(); - uint unusedPid; const string noArgs = ""; const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None; await Task.Run(() => { try { - appManager.ActivateApplication(UserModelId, noArgs, noFlags, out unusedPid); + appManager.ActivateApplication(UserModelId, noArgs, noFlags, out _); } catch (Exception) { @@ -366,19 +364,13 @@ namespace Flow.Launcher.Plugin.Program.Programs public Application(AppxPackageHelper.IAppxManifestApplication manifestApp, UWP package) { // This is done because we cannot use the keyword 'out' along with a property - string tmpUserModelId; - string tmpUniqueIdentifier; - string tmpDisplayName; - string tmpDescription; - string tmpBackgroundColor; - string tmpEntryPoint; - manifestApp.GetAppUserModelId(out tmpUserModelId); - manifestApp.GetAppUserModelId(out tmpUniqueIdentifier); - manifestApp.GetStringValue("DisplayName", out tmpDisplayName); - manifestApp.GetStringValue("Description", out tmpDescription); - manifestApp.GetStringValue("BackgroundColor", out tmpBackgroundColor); - manifestApp.GetStringValue("EntryPoint", out tmpEntryPoint); + manifestApp.GetAppUserModelId(out string tmpUserModelId); + manifestApp.GetAppUserModelId(out string tmpUniqueIdentifier); + manifestApp.GetStringValue("DisplayName", out string tmpDisplayName); + manifestApp.GetStringValue("Description", out string tmpDescription); + manifestApp.GetStringValue("BackgroundColor", out string tmpBackgroundColor); + manifestApp.GetStringValue("EntryPoint", out string tmpEntryPoint); UserModelId = tmpUserModelId; UniqueIdentifier = tmpUniqueIdentifier; @@ -464,9 +456,8 @@ namespace Flow.Launcher.Plugin.Program.Programs }; if (logoKeyFromVersion.ContainsKey(Package.Version)) { - string logoUri; var key = logoKeyFromVersion[Package.Version]; - app.GetStringValue(key, out logoUri); + app.GetStringValue(key, out string logoUri); return logoUri; } else From 8a2a71000ec369bf17d57d3c9311840e731ea41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 15 Feb 2021 17:58:15 +0800 Subject: [PATCH 434/442] Use Channel instead of BufferBlock --- Flow.Launcher/ViewModel/MainViewModel.cs | 31 ++++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index a12912b67..0cf4b11dc 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -19,6 +19,7 @@ using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; using Flow.Launcher.Infrastructure.Logger; +using System.Threading.Channels; namespace Flow.Launcher.ViewModel { @@ -46,7 +47,7 @@ namespace Flow.Launcher.ViewModel private readonly Internationalization _translator = InternationalizationManager.Instance; - private BufferBlock _resultsUpdateQueue; + private Channel _resultsUpdateQueue; private Task _resultsViewUpdateTask; #endregion @@ -85,18 +86,21 @@ namespace Flow.Launcher.ViewModel private void RegisterViewUpdate() { - _resultsUpdateQueue = new BufferBlock(); + _resultsUpdateQueue = Channel.CreateUnbounded(); _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); async Task updateAction() { var queue = new Dictionary(); - while (await _resultsUpdateQueue.OutputAvailableAsync()) + var queueReader = _resultsUpdateQueue.Reader; + + // it is not supposed to be false because it won't be complete + while (await queueReader.WaitToReadAsync()) { queue.Clear(); await Task.Delay(20); - while (_resultsUpdateQueue.TryReceive(out var item)) + await foreach (var item in queueReader.ReadAllAsync()) { if (!item.Token.IsCancellationRequested) queue[item.ID] = item; @@ -104,6 +108,8 @@ namespace Flow.Launcher.ViewModel UpdateResultView(queue.Values); } + + Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends"); }; void continueAction(Task t) @@ -128,7 +134,10 @@ namespace Flow.Launcher.ViewModel if (e.Query.RawQuery == QueryText) // TODO: allow cancellation { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - _resultsUpdateQueue.Post(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken)); + if (!_resultsUpdateQueue.Writer.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken))) + { + Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); + }; } }; } @@ -529,9 +538,15 @@ namespace Flow.Launcher.ViewModel await Task.Yield(); var results = await PluginManager.QueryForPlugin(plugin, query, currentCancellationToken); - if (!currentCancellationToken.IsCancellationRequested && results != null) - _resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, - currentCancellationToken)); + if (currentCancellationToken.IsCancellationRequested || results == null) + { + return; + } + + if (!_resultsUpdateQueue.Writer.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken))) + { + Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); + }; } }, currentCancellationToken) .ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception), From d36140c51ce0b3c1a729092c1efff8f218e59582 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 15 Feb 2021 21:14:50 +1100 Subject: [PATCH 435/442] version bump PluginsManager --- Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 4cba7ec83..f95c0d60d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.6.4", + "Version": "1.7.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", From a7811f312c1add5f6a16e44970ebd010173abed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 15 Feb 2021 18:19:30 +0800 Subject: [PATCH 436/442] fix an issue --- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 0cf4b11dc..3e71adcd1 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -98,15 +98,15 @@ namespace Flow.Launcher.ViewModel // it is not supposed to be false because it won't be complete while (await queueReader.WaitToReadAsync()) { - queue.Clear(); await Task.Delay(20); - await foreach (var item in queueReader.ReadAllAsync()) + while (queueReader.TryRead(out var item)) { if (!item.Token.IsCancellationRequested) queue[item.ID] = item; } UpdateResultView(queue.Values); + queue.Clear(); } Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends"); From 9ebebfd0ef9fcaaada22c096adb55f093969e08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 15 Feb 2021 18:27:02 +0800 Subject: [PATCH 437/442] No need to cancel error reporting --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index e342a5f94..9caac1a47 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -213,7 +213,6 @@ namespace Flow.Launcher.Core.Plugin { using var standardError = process.StandardError; var error = await standardError.ReadToEndAsync(); - token.ThrowIfCancellationRequested(); if (!string.IsNullOrEmpty(error)) { From b017bdeed435f810909e2afd2c9f49bb41ba3f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 15 Feb 2021 18:37:42 +0800 Subject: [PATCH 438/442] fix format --- Flow.Launcher/ViewModel/MainViewModel.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 3e71adcd1..90bdc38c8 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -538,10 +538,7 @@ namespace Flow.Launcher.ViewModel await Task.Yield(); var results = await PluginManager.QueryForPlugin(plugin, query, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested || results == null) - { - return; - } + if (currentCancellationToken.IsCancellationRequested || results == null) return; if (!_resultsUpdateQueue.Writer.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken))) { From 350499a4801a9a60ba00a811b79bc7785b4da33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 16 Feb 2021 02:12:28 +0800 Subject: [PATCH 439/442] Optimize Logic and code --- Flow.Launcher/ViewModel/MainViewModel.cs | 17 +++++++++-------- Flow.Launcher/ViewModel/ResultsForUpdate.cs | 9 +-------- Flow.Launcher/ViewModel/ResultsViewModel.cs | 11 ++++------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 90bdc38c8..c1382e51e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -47,7 +47,7 @@ namespace Flow.Launcher.ViewModel private readonly Internationalization _translator = InternationalizationManager.Instance; - private Channel _resultsUpdateQueue; + private ChannelWriter _resultsUpdateChannelWriter; private Task _resultsViewUpdateTask; #endregion @@ -86,20 +86,21 @@ namespace Flow.Launcher.ViewModel private void RegisterViewUpdate() { - _resultsUpdateQueue = Channel.CreateUnbounded(); + var resultUpdateChannel = Channel.CreateUnbounded(); + _resultsUpdateChannelWriter = resultUpdateChannel.Writer; _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); async Task updateAction() { var queue = new Dictionary(); - var queueReader = _resultsUpdateQueue.Reader; + var channelReader = resultUpdateChannel.Reader; // it is not supposed to be false because it won't be complete - while (await queueReader.WaitToReadAsync()) + while (await channelReader.WaitToReadAsync()) { await Task.Delay(20); - while (queueReader.TryRead(out var item)) + while (channelReader.TryRead(out var item)) { if (!item.Token.IsCancellationRequested) queue[item.ID] = item; @@ -134,7 +135,7 @@ namespace Flow.Launcher.ViewModel if (e.Query.RawQuery == QueryText) // TODO: allow cancellation { PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - if (!_resultsUpdateQueue.Writer.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken))) + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken))) { Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); }; @@ -539,8 +540,8 @@ namespace Flow.Launcher.ViewModel var results = await PluginManager.QueryForPlugin(plugin, query, currentCancellationToken); if (currentCancellationToken.IsCancellationRequested || results == null) return; - - if (!_resultsUpdateQueue.Writer.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken))) + + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken))) { Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); }; diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs index be48f53c1..87d526fd6 100644 --- a/Flow.Launcher/ViewModel/ResultsForUpdate.cs +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -6,7 +6,7 @@ using System.Threading; namespace Flow.Launcher.ViewModel { - public class ResultsForUpdate + public struct ResultsForUpdate { public List Results { get; } @@ -16,13 +16,6 @@ namespace Flow.Launcher.ViewModel public Query Query { get; } public CancellationToken Token { get; } - public ResultsForUpdate(List results, string resultID, CancellationToken token) - { - Results = results; - ID = resultID; - Token = token; - } - public ResultsForUpdate(List results, PluginMetadata metadata, Query query, CancellationToken token) { Results = results; diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index feab3a751..41f16f4f2 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -183,13 +183,12 @@ namespace Flow.Launcher.ViewModel private List NewResults(List newRawResults, string resultId) { if (newRawResults.Count == 0) - return Results.ToList(); + return Results; - var results = Results as IEnumerable; var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)); - return results.Where(r => r.Result.PluginID != resultId) + return Results.Where(r => r.Result.PluginID != resultId) .Concat(newResults) .OrderByDescending(r => r.Result.Score) .ToList(); @@ -198,11 +197,9 @@ namespace Flow.Launcher.ViewModel private List NewResults(IEnumerable resultsForUpdates) { if (!resultsForUpdates.Any()) - return Results.ToList(); + return Results; - var results = Results as IEnumerable; - - return results.Where(r => r != null && !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID)) + return Results.Where(r => r != null && !resultsForUpdates.Any(u => u.ID == r.Result.PluginID)) .Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings))) .OrderByDescending(rv => rv.Result.Score) .ToList(); From abd721a06cff43e1490843b8f9b995e064cc1c62 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 16 Feb 2021 06:14:13 +1100 Subject: [PATCH 440/442] fix formatting --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 9caac1a47..7a088bd09 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -31,7 +31,6 @@ namespace Flow.Launcher.Core.Plugin protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest); protected abstract string ExecuteContextMenu(Result selectedResult); - public List LoadContextMenus(Result selectedResult) { string output = ExecuteContextMenu(selectedResult); @@ -235,8 +234,6 @@ namespace Flow.Launcher.Core.Plugin } } - - public async Task> QueryAsync(Query query, CancellationToken token) { var output = await ExecuteQueryAsync(query, token); @@ -257,4 +254,4 @@ namespace Flow.Launcher.Core.Plugin return Task.CompletedTask; } } -} \ No newline at end of file +} From 307cb305f064f2dd21886f7f163a1a789e3cad3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 16 Feb 2021 10:50:48 +0800 Subject: [PATCH 441/442] Remove unused check for Stream length --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs | 4 ++-- .../SuggestionSources/Baidu.cs | 2 +- .../SuggestionSources/Bing.cs | 7 ++----- .../SuggestionSources/Google.cs | 7 ++----- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 700a7d509..66eb0f0ec 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -195,11 +195,11 @@ namespace Flow.Launcher.Core.Plugin catch (OperationCanceledException) { // null will be fine since the results will only be added into queue if the token hasn't been cancelled - return results = null; + return null; } catch (Exception e) { - Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e); + Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e); } return results; diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index f76e28112..4135bb59a 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -123,12 +123,12 @@ namespace Flow.Launcher.Plugin.WebSearch var source = _settings.SelectedSuggestion; if (source != null) { - var suggestions = await source.Suggestions(keyword, token); + var suggestions = await source.Suggestions(keyword, token).ConfigureAwait(false); if (token.IsCancellationRequested) return null; - var resultsFromSuggestion = suggestions.Select(o => new Result + var resultsFromSuggestion = suggestions?.Select(o => new Result { Title = o, SubTitle = subtitle, diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index b7e2017f9..385a9f8b5 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -32,7 +32,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources catch (HttpRequestException e) { Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e); - return new List(); + return null; } if (string.IsNullOrEmpty(result)) return new List(); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index ffde2fda2..e505d78cf 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -24,16 +24,13 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources const string api = "https://api.bing.com/qsonhs.aspx?q="; using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); - - if (resultStream.Length == 0) - return new List(); // this handles the cancellation - + json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token)).RootElement.GetProperty("AS"); } catch (TaskCanceledException) { - return null; + return new List(); } catch (HttpRequestException e) { diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index c33ebd7e1..928c7aaa2 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -24,19 +24,16 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); - if (resultStream.Length == 0) - return new List(); - json = await JsonDocument.ParseAsync(resultStream); } catch (TaskCanceledException) { - return null; + return new List(); } catch (HttpRequestException e) { - Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); + // Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); return new List(); } catch (JsonException e) From 2bc290a30208d81588b19a38c36fd542d956dae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 16 Feb 2021 11:27:12 +0800 Subject: [PATCH 442/442] uncomment exception --- .../Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 928c7aaa2..c075f4d5d 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -33,7 +33,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources } catch (HttpRequestException e) { - // Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); + Log.Exception("|Google.Suggestions|Can't get suggestion from google", e); return new List(); } catch (JsonException e)