diff --git a/.gitignore b/.gitignore index 28366cd03..a293100e6 100644 --- a/.gitignore +++ b/.gitignore @@ -300,4 +300,8 @@ migrateToAutomaticPackageRestore.ps1 *.pyc *.diagsession Output-Performance.txt -*.diff \ No newline at end of file +*.diff + +# vscode +.vscode +.history \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..4596aa084 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,15 @@ + + + + + false + + + false + + + + \ No newline at end of file diff --git a/Doc/Default Icons/app_missing_img.png b/Doc/Default Icons/app_missing_img.png new file mode 100644 index 000000000..b07eb4262 Binary files /dev/null and b/Doc/Default Icons/app_missing_img.png differ diff --git a/Doc/Default Icons/app_missing_img_01.png b/Doc/Default Icons/app_missing_img_01.png new file mode 100644 index 000000000..2cc466ed0 Binary files /dev/null and b/Doc/Default Icons/app_missing_img_01.png differ diff --git a/Doc/Default Icons/app_missing_img_01.svg b/Doc/Default Icons/app_missing_img_01.svg new file mode 100644 index 000000000..88b84858a --- /dev/null +++ b/Doc/Default Icons/app_missing_img_01.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_02.png b/Doc/Default Icons/app_missing_img_02.png new file mode 100644 index 000000000..285a68890 Binary files /dev/null and b/Doc/Default Icons/app_missing_img_02.png differ diff --git a/Doc/Default Icons/app_missing_img_02.svg b/Doc/Default Icons/app_missing_img_02.svg new file mode 100644 index 000000000..190ecf469 --- /dev/null +++ b/Doc/Default Icons/app_missing_img_02.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_03.png b/Doc/Default Icons/app_missing_img_03.png new file mode 100644 index 000000000..11d5466f0 Binary files /dev/null and b/Doc/Default Icons/app_missing_img_03.png differ diff --git a/Doc/Default Icons/app_missing_img_03.svg b/Doc/Default Icons/app_missing_img_03.svg new file mode 100644 index 000000000..904380091 --- /dev/null +++ b/Doc/Default Icons/app_missing_img_03.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_buttons.png b/Doc/Default Icons/app_missing_img_buttons.png new file mode 100644 index 000000000..62de71f21 Binary files /dev/null and b/Doc/Default Icons/app_missing_img_buttons.png differ diff --git a/Doc/Default Icons/app_missing_img_buttons.svg b/Doc/Default Icons/app_missing_img_buttons.svg new file mode 100644 index 000000000..b9d5e1259 --- /dev/null +++ b/Doc/Default Icons/app_missing_img_buttons.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_fluent.png b/Doc/Default Icons/app_missing_img_fluent.png new file mode 100644 index 000000000..eafd1bb35 Binary files /dev/null and b/Doc/Default Icons/app_missing_img_fluent.png differ diff --git a/Doc/Default Icons/app_missing_img_fluent.svg b/Doc/Default Icons/app_missing_img_fluent.svg new file mode 100644 index 000000000..7a201cdc3 --- /dev/null +++ b/Doc/Default Icons/app_missing_img_fluent.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_huge.png b/Doc/Default Icons/app_missing_img_huge.png new file mode 100644 index 000000000..70aea308d Binary files /dev/null and b/Doc/Default Icons/app_missing_img_huge.png differ diff --git a/Doc/Default Icons/app_missing_img_huge.svg b/Doc/Default Icons/app_missing_img_huge.svg new file mode 100644 index 000000000..874a9a0ca --- /dev/null +++ b/Doc/Default Icons/app_missing_img_huge.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_lightblue.png b/Doc/Default Icons/app_missing_img_lightblue.png new file mode 100644 index 000000000..3b262fc9b Binary files /dev/null and b/Doc/Default Icons/app_missing_img_lightblue.png differ diff --git a/Doc/Default Icons/app_missing_img_lightblue.svg b/Doc/Default Icons/app_missing_img_lightblue.svg new file mode 100644 index 000000000..da713b413 --- /dev/null +++ b/Doc/Default Icons/app_missing_img_lightblue.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_lightblue_buttons.png b/Doc/Default Icons/app_missing_img_lightblue_buttons.png new file mode 100644 index 000000000..17de0a6fe Binary files /dev/null and b/Doc/Default Icons/app_missing_img_lightblue_buttons.png differ diff --git a/Doc/Default Icons/app_missing_img_lightblue_buttons.svg b/Doc/Default Icons/app_missing_img_lightblue_buttons.svg new file mode 100644 index 000000000..7251fb9f6 --- /dev/null +++ b/Doc/Default Icons/app_missing_img_lightblue_buttons.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_minimal.png b/Doc/Default Icons/app_missing_img_minimal.png new file mode 100644 index 000000000..e66ea6d6e Binary files /dev/null and b/Doc/Default Icons/app_missing_img_minimal.png differ diff --git a/Doc/Default Icons/app_missing_img_minimal.svg b/Doc/Default Icons/app_missing_img_minimal.svg new file mode 100644 index 000000000..d2b6d22fe --- /dev/null +++ b/Doc/Default Icons/app_missing_img_minimal.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/Default Icons/app_missing_img_minimal_buttons.png b/Doc/Default Icons/app_missing_img_minimal_buttons.png new file mode 100644 index 000000000..d4f240909 Binary files /dev/null and b/Doc/Default Icons/app_missing_img_minimal_buttons.png differ diff --git a/Doc/Default Icons/app_missing_img_minimal_buttons.svg b/Doc/Default Icons/app_missing_img_minimal_buttons.svg new file mode 100644 index 000000000..131ffadf8 --- /dev/null +++ b/Doc/Default Icons/app_missing_img_minimal_buttons.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doc/app_missing_img.png b/Doc/app_missing_img.png new file mode 100644 index 000000000..11d5466f0 Binary files /dev/null and b/Doc/app_missing_img.png differ diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index 44e4434be..5bca087b8 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -95,7 +95,7 @@ namespace Flow.Launcher.Core.Configuration public void MoveUserDataFolder(string fromLocation, string toLocation) { - FilesFolders.Copy(fromLocation, toLocation); + FilesFolders.CopyAll(fromLocation, toLocation); VerifyUserDataAfterMove(fromLocation, toLocation); } diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 87c390d34..189a6669e 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -54,12 +54,7 @@ - - - - - diff --git a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs new file mode 100644 index 000000000..b9b878a7b --- /dev/null +++ b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs @@ -0,0 +1,57 @@ +using Flow.Launcher.Infrastructure; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; + +namespace Flow.Launcher.Core.Plugin +{ + internal class PluginAssemblyLoader : AssemblyLoadContext + { + private readonly AssemblyDependencyResolver dependencyResolver; + + private readonly AssemblyDependencyResolver referencedPluginPackageDependencyResolver; + + private readonly AssemblyName assemblyName; + + internal PluginAssemblyLoader(string assemblyFilePath) + { + dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath); + assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath)); + + referencedPluginPackageDependencyResolver = + new AssemblyDependencyResolver(Path.Combine(Constant.ProgramDirectory, "Flow.Launcher.Plugin.dll")); + } + + internal Assembly LoadAssemblyAndDependencies() + { + return LoadFromAssemblyName(assemblyName); + } + + protected override Assembly Load(AssemblyName assemblyName) + { + string assemblyPath = dependencyResolver.ResolveAssemblyToPath(assemblyName); + + // When resolving dependencies, ignore assembly depenedencies that already exits with Flow.Launcher.Plugin + // Otherwise will get unexpected behaviour with plugins, e.g. JsonIgnore attribute not honored in WebSearch or other plugins + // that use Newtonsoft.Json + if (assemblyPath == null || ExistsInReferencedPluginPackage(assemblyName)) + return null; + + return LoadFromAssemblyPath(assemblyPath); + } + + internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type) + { + var allTypes = assembly.ExportedTypes; + + return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(type)); + } + + internal bool ExistsInReferencedPluginPackage(AssemblyName assemblyName) + { + return referencedPluginPackageDependencyResolver.ResolveAssemblyToPath(assemblyName) != null; + } + } +} diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs deleted file mode 100644 index 7b980a3ee..000000000 --- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.IO; -using System.Windows; -using ICSharpCode.SharpZipLib.Zip; -using Newtonsoft.Json; -using Flow.Launcher.Plugin; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.Logger; - -namespace Flow.Launcher.Core.Plugin -{ - internal class PluginInstaller - { - internal static void Install(string path) - { - if (File.Exists(path)) - { - string tempFolder = Path.Combine(Path.GetTempPath(), "flowlauncher", "plugins"); - if (Directory.Exists(tempFolder)) - { - Directory.Delete(tempFolder, true); - } - UnZip(path, tempFolder, true); - - string jsonPath = Path.Combine(tempFolder, Constant.PluginMetadataFileName); - if (!File.Exists(jsonPath)) - { - MessageBox.Show("Install failed: plugin config is missing"); - return; - } - - PluginMetadata plugin = GetMetadataFromJson(tempFolder); - if (plugin == null || plugin.Name == null) - { - MessageBox.Show("Install failed: plugin config is invalid"); - return; - } - - string pluginFolderPath = Infrastructure.UserSettings.DataLocation.PluginsDirectory; - - string newPluginName = plugin.Name - .Replace("/", "_") - .Replace("\\", "_") - .Replace(":", "_") - .Replace("<", "_") - .Replace(">", "_") - .Replace("?", "_") - .Replace("*", "_") - .Replace("|", "_") - + "-" + Guid.NewGuid(); - - string newPluginPath = Path.Combine(pluginFolderPath, newPluginName); - - string content = $"Do you want to install following plugin?{Environment.NewLine}{Environment.NewLine}" + - $"Name: {plugin.Name}{Environment.NewLine}" + - $"Version: {plugin.Version}{Environment.NewLine}" + - $"Author: {plugin.Author}"; - PluginPair existingPlugin = PluginManager.GetPluginForId(plugin.ID); - - if (existingPlugin != null) - { - content = $"Do you want to update following plugin?{Environment.NewLine}{Environment.NewLine}" + - $"Name: {plugin.Name}{Environment.NewLine}" + - $"Old Version: {existingPlugin.Metadata.Version}" + - $"{Environment.NewLine}New Version: {plugin.Version}" + - $"{Environment.NewLine}Author: {plugin.Author}"; - } - - var result = MessageBox.Show(content, "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question); - if (result == MessageBoxResult.Yes) - { - if (existingPlugin != null && Directory.Exists(existingPlugin.Metadata.PluginDirectory)) - { - //when plugin is in use, we can't delete them. That's why we need to make plugin folder a random name - File.Create(Path.Combine(existingPlugin.Metadata.PluginDirectory, "NeedDelete.txt")).Close(); - } - - Directory.Move(tempFolder, newPluginPath); - - //exsiting plugins may be has loaded by application, - //if we try to delelte those kind of plugins, we will get a error that indicate the - //file is been used now. - //current solution is to restart Flow Launcher. Ugly. - //if (MainWindow.Initialized) - //{ - // Plugins.Initialize(); - //} - if (MessageBox.Show($"You have installed plugin {plugin.Name} successfully.{Environment.NewLine}" + - "Restart Flow Launcher to take effect?", - "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) - { - PluginManager.API.RestartApp(); - } - } - } - } - - private static PluginMetadata GetMetadataFromJson(string pluginDirectory) - { - string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName); - PluginMetadata metadata; - - if (!File.Exists(configPath)) - { - return null; - } - - try - { - metadata = JsonConvert.DeserializeObject(File.ReadAllText(configPath)); - metadata.PluginDirectory = pluginDirectory; - } - catch (Exception e) - { - Log.Exception($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: invalid json format", e); - return null; - } - - if (!AllowedLanguage.IsAllowed(metadata.Language)) - { - Log.Error($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: invalid language {metadata.Language}"); - return null; - } - if (!File.Exists(metadata.ExecuteFilePath)) - { - Log.Error($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: file {metadata.ExecuteFilePath} doesn't exist"); - return null; - } - - return metadata; - } - - /// - /// unzip plugin contents to the given directory. - /// - /// The path to the zip file. - /// The output directory. - /// overwirte - private static void UnZip(string zipFile, string strDirectory, bool overWrite) - { - if (strDirectory == "") - strDirectory = Directory.GetCurrentDirectory(); - - using (ZipInputStream zipStream = new ZipInputStream(File.OpenRead(zipFile))) - { - ZipEntry theEntry; - - while ((theEntry = zipStream.GetNextEntry()) != null) - { - var pathToZip = theEntry.Name; - var directoryName = String.IsNullOrEmpty(pathToZip) ? "" : Path.GetDirectoryName(pathToZip); - var fileName = Path.GetFileName(pathToZip); - var destinationDir = Path.Combine(strDirectory, directoryName); - var destinationFile = Path.Combine(destinationDir, fileName); - - Directory.CreateDirectory(destinationDir); - - if (String.IsNullOrEmpty(fileName) || (File.Exists(destinationFile) && !overWrite)) - continue; - - using (FileStream streamWriter = File.Create(destinationFile)) - { - zipStream.CopyTo(streamWriter); - } - } - } - } - } -} diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 5cde9de83..3b697a1ee 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -133,11 +133,6 @@ namespace Flow.Launcher.Core.Plugin } } - public static void InstallPlugin(string path) - { - PluginInstaller.Install(path); - } - public static List ValidPluginsForQuery(Query query) { if (NonGlobalPlugins.ContainsKey(query.ActionKeyword)) diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 1025f9bae..224dbd85e 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -41,9 +41,9 @@ namespace Flow.Launcher.Core.Plugin { #if DEBUG - var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(metadata.ExecuteFilePath); - var types = assembly.GetTypes(); - var type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin))); + 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; @@ -51,10 +51,10 @@ namespace Flow.Launcher.Core.Plugin try { - assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(metadata.ExecuteFilePath); + var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath); + assembly = assemblyLoader.LoadAssemblyAndDependencies(); - var types = assembly.GetTypes(); - var type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin))); + var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin)); plugin = (IPlugin)Activator.CreateInstance(type); } diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 1df6f2a04..64b949cbb 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -88,7 +88,6 @@ namespace Flow.Launcher.Core.Resource { language = language.NonNull(); - Settings.Language = language.LanguageCode; RemoveOldLanguageFiles(); if (language != AvailableLanguages.English) @@ -96,6 +95,7 @@ namespace Flow.Launcher.Core.Resource LoadLanguage(language); } UpdatePluginMetadataTranslations(); + Settings.Language = language.LanguageCode; } diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 2075137d0..a5f12bbb9 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -21,7 +21,7 @@ namespace Flow.Launcher.Core.Resource private ResourceDictionary _oldResource; private string _oldTheme; public Settings Settings { get; set; } - private const string Folder = "Themes"; + private const string Folder = Constant.Themes; private const string Extension = ".xaml"; private string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder); private string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder); diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 4ad8b19be..d8bc2b6dc 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -91,7 +91,7 @@ namespace Flow.Launcher.Core if (DataLocation.PortableDataLocationInUse()) { var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}"; - FilesFolders.Copy(DataLocation.PortableDataPath, targetDestination); + 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}"); diff --git a/Flow.Launcher.Infrastructure/Alphabet.cs b/Flow.Launcher.Infrastructure/Alphabet.cs deleted file mode 100644 index 7e24a8206..000000000 --- a/Flow.Launcher.Infrastructure/Alphabet.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using hyjiacan.util.p4n; -using hyjiacan.util.p4n.format; -using JetBrains.Annotations; -using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Infrastructure.Storage; -using Flow.Launcher.Infrastructure.UserSettings; - -namespace Flow.Launcher.Infrastructure -{ - public interface IAlphabet - { - string Translate(string stringToTranslate); - } - - public class Alphabet : IAlphabet - { - private readonly HanyuPinyinOutputFormat Format = new HanyuPinyinOutputFormat(); - private ConcurrentDictionary PinyinCache; - private BinaryStorage> _pinyinStorage; - private Settings _settings; - - public void Initialize([NotNull] Settings settings) - { - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - InitializePinyinHelpers(); - } - - private void InitializePinyinHelpers() - { - Format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); - - Stopwatch.Normal("|Flow Launcher.Infrastructure.Alphabet.Initialize|Preload pinyin cache", () => - { - _pinyinStorage = new BinaryStorage>("Pinyin"); - - lock(_pinyinStorage) - { - var loaded = _pinyinStorage.TryLoad(new Dictionary()); - - PinyinCache = new ConcurrentDictionary(loaded); - } - - // force pinyin library static constructor initialize - PinyinHelper.toHanyuPinyinStringArray('T', Format); - }); - Log.Info($"|Flow Launcher.Infrastructure.Alphabet.Initialize|Number of preload pinyin combination<{PinyinCache.Count}>"); - } - - public string Translate(string str) - { - return ConvertChineseCharactersToPinyin(str); - } - - public string ConvertChineseCharactersToPinyin(string source) - { - if (!_settings.ShouldUsePinyin) - return source; - - if (string.IsNullOrEmpty(source)) - return source; - - if (!ContainsChinese(source)) - return source; - - var combination = PinyinCombination(source); - - var pinyinArray=combination.Select(x => string.Join("", x)); - var acronymArray = combination.Select(Acronym).Distinct(); - - var joinedSingleStringCombination = new StringBuilder(); - var all = acronymArray.Concat(pinyinArray); - all.ToList().ForEach(x => joinedSingleStringCombination.Append(x)); - - return joinedSingleStringCombination.ToString(); - } - - public void Save() - { - if (!_settings.ShouldUsePinyin) - { - return; - } - - lock(_pinyinStorage) - { - _pinyinStorage.Save(PinyinCache.ToDictionary(i => i.Key, i => i.Value)); - } - } - - private static string[] EmptyStringArray = new string[0]; - private static string[][] Empty2DStringArray = new string[0][]; - - /// - /// replace chinese character with pinyin, non chinese character won't be modified - /// Because we don't have words dictionary, so we can only return all possiblie pinyin combination - /// e.g. 音乐 will return yinyue and yinle - /// should be word or sentence, instead of single character. e.g. 微软 - /// - public string[][] PinyinCombination(string characters) - { - if (!_settings.ShouldUsePinyin || string.IsNullOrEmpty(characters)) - { - return Empty2DStringArray; - } - - if (!PinyinCache.ContainsKey(characters)) - { - var allPinyins = new List(); - foreach (var c in characters) - { - var pinyins = PinyinHelper.toHanyuPinyinStringArray(c, Format); - if (pinyins != null) - { - var r = pinyins.Distinct().ToArray(); - allPinyins.Add(r); - } - else - { - var r = new[] { c.ToString() }; - allPinyins.Add(r); - } - } - - var combination = allPinyins.Aggregate(Combination).Select(c => c.Split(';')).ToArray(); - PinyinCache[characters] = combination; - return combination; - } - else - { - return PinyinCache[characters]; - } - } - - public string Acronym(string[] pinyin) - { - var acronym = string.Join("", pinyin.Select(p => p[0])); - return acronym; - } - - public bool ContainsChinese(string word) - { - if (!_settings.ShouldUsePinyin) - { - return false; - } - - if (word.Length > 40) - { - //Skip strings that are too long string for Pinyin conversion. - return false; - } - - var chinese = word.Select(PinyinHelper.toHanyuPinyinStringArray) - .Any(p => p != null); - return chinese; - } - - private string[] Combination(string[] array1, string[] array2) - { - if (!_settings.ShouldUsePinyin) - { - return EmptyStringArray; - } - - var combination = ( - from a1 in array1 - from a2 in array2 - select $"{a1};{a2}" - ).ToArray(); - return combination; - } - } -} diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index a8c72fb12..df1464048 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -23,13 +23,17 @@ namespace Flow.Launcher.Infrastructure public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion; public static readonly int ThumbnailSize = 64; - public static readonly string DefaultIcon = Path.Combine(ProgramDirectory, "Images", "app.png"); - public static readonly string ErrorIcon = Path.Combine(ProgramDirectory, "Images", "app_error.png"); + private static readonly string ImagesDirectory = Path.Combine(ProgramDirectory, "Images"); + public static readonly string DefaultIcon = Path.Combine(ImagesDirectory, "app.png"); + public static readonly string ErrorIcon = Path.Combine(ImagesDirectory, "app_error.png"); + public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png"); public static string PythonPath; public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.png"; public const string DefaultTheme = "Darker"; + + public const string Themes = "Themes"; } } diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 410d11536..28d4c0e1f 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -49,14 +49,11 @@ - - - - + diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 5d7224c5b..b1c09024f 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -2,44 +2,84 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using System.Windows.Media; namespace Flow.Launcher.Infrastructure.Image { [Serializable] + public class ImageUsage + { + + public int usage; + public ImageSource imageSource; + + public ImageUsage(int usage, ImageSource image) + { + this.usage = usage; + imageSource = image; + } + } + public class ImageCache { - private const int MaxCached = 5000; - public ConcurrentDictionary Usage = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _data = new ConcurrentDictionary(); - + 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) + { + Data[key] = new ImageUsage(usage[key], null); + } + } public ImageSource this[string path] { get { - Usage.AddOrUpdate(path, 1, (k, v) => v + 1); - var i = _data[path]; - return i; - } - set { _data[path] = value; } - } + if (Data.TryGetValue(path, out var value)) + { + value.usage++; + return value.imageSource; + } - public Dictionary CleanupAndToDictionary() - => Usage - .OrderByDescending(o => o.Value) - .Take(MaxCached) - .ToDictionary(i => i.Key, i => i.Value); + return null; + } + set + { + Data.AddOrUpdate( + path, + new ImageUsage(0, value), + (k, v) => + { + v.imageSource = value; + v.usage++; + return v; + } + ); + + // To prevent the dictionary from drastically increasing in size by caching images, the dictionary size is not allowed to grow more than the permissibleFactor * maxCached size + // This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time + 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.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) + Data.TryRemove(key, out _); + } + } + } public bool ContainsKey(string key) { - var contains = _data.ContainsKey(key); + var contains = Data.ContainsKey(key) && Data[key] != null; return contains; } public int CacheSize() { - return _data.Count; + return Data.Count; } /// @@ -47,8 +87,7 @@ namespace Flow.Launcher.Infrastructure.Image /// public int UniqueImagesInCache() { - return _data.Values.Distinct().Count(); + return Data.Values.Select(x => x.imageSource).Distinct().Count(); } } - -} +} \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 0bf575337..ac333d567 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -13,12 +13,13 @@ namespace Flow.Launcher.Infrastructure.Image { public static class ImageLoader { - private static readonly ImageCache _imageCache = new ImageCache(); - private static readonly ConcurrentDictionary _guidToKey = new ConcurrentDictionary(); - private static readonly bool _enableHashImage = true; - + private static readonly ImageCache ImageCache = new ImageCache(); private static BinaryStorage> _storage; + 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 = { @@ -36,25 +37,25 @@ namespace Flow.Launcher.Infrastructure.Image _storage = new BinaryStorage>("Image"); _hashGenerator = new ImageHashGenerator(); - _imageCache.Usage = LoadStorageToConcurrentDictionary(); + var usage = LoadStorageToConcurrentDictionary(); - foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon }) + foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); img.Freeze(); - _imageCache[icon] = img; + ImageCache[icon] = img; } Task.Run(() => { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { - _imageCache.Usage.AsParallel().ForAll(x => + ImageCache.Data.AsParallel().ForAll(x => { Load(x.Key); }); }); - Log.Info($"|ImageLoader.Initialize|Number of preload images is <{_imageCache.Usage.Count}>, Images Number: {_imageCache.CacheSize()}, Unique Items {_imageCache.UniqueImagesInCache()}"); + Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); }); } @@ -62,13 +63,13 @@ namespace Flow.Launcher.Infrastructure.Image { lock (_storage) { - _storage.Save(_imageCache.CleanupAndToDictionary()); + _storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, x => x.usage)); } } private static ConcurrentDictionary LoadStorageToConcurrentDictionary() { - lock(_storage) + lock (_storage) { var loaded = _storage.TryLoad(new Dictionary()); @@ -106,11 +107,11 @@ namespace Flow.Launcher.Infrastructure.Image { if (string.IsNullOrEmpty(path)) { - return new ImageResult(_imageCache[Constant.ErrorIcon], ImageType.Error); + return new ImageResult(ImageCache[Constant.MissingImgIcon], ImageType.Error); } - if (_imageCache.ContainsKey(path)) + if (ImageCache.ContainsKey(path)) { - return new ImageResult(_imageCache[path], ImageType.Cache); + return new ImageResult(ImageCache[path], ImageType.Cache); } if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) @@ -139,8 +140,8 @@ namespace Flow.Launcher.Infrastructure.Image Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on first try", e); Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2); - ImageSource image = _imageCache[Constant.ErrorIcon]; - _imageCache[path] = image; + ImageSource image = ImageCache[Constant.MissingImgIcon]; + ImageCache[path] = image; imageResult = new ImageResult(image, ImageType.Error); } } @@ -191,8 +192,8 @@ namespace Flow.Launcher.Infrastructure.Image } else { - image = _imageCache[Constant.ErrorIcon]; - path = Constant.ErrorIcon; + image = ImageCache[Constant.MissingImgIcon]; + path = Constant.MissingImgIcon; } if (type != ImageType.Error) @@ -212,33 +213,37 @@ namespace Flow.Launcher.Infrastructure.Image option); } + public static bool CacheContainImage(string path) + { + return ImageCache.ContainsKey(path) && ImageCache[path] != null; + } + public static ImageSource Load(string path, bool loadFullImage = false) { var imageResult = LoadInternal(path, loadFullImage); var img = imageResult.ImageSource; if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache) - { - // we need to get image hash - string hash = _enableHashImage ? _hashGenerator.GetHashFromImage(img) : null; + { // we need to get image hash + string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; if (hash != null) { - if (_guidToKey.TryGetValue(hash, out string key)) - { - // image already exists - img = _imageCache[key]; + + if (GuidToKey.TryGetValue(hash, out string key)) + { // image already exists + img = ImageCache[key] ?? img; } else - { - // new guid - _guidToKey[hash] = path; + { // new guid + GuidToKey[hash] = path; } } // update cache - _imageCache[path] = img; + ImageCache[path] = img; } + return img; } diff --git a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs new file mode 100644 index 000000000..80fd12820 --- /dev/null +++ b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Text; +using JetBrains.Annotations; +using Flow.Launcher.Infrastructure.UserSettings; +using ToolGood.Words.Pinyin; + +namespace Flow.Launcher.Infrastructure +{ + public interface IAlphabet + { + string Translate(string stringToTranslate); + } + + public class PinyinAlphabet : IAlphabet + { + private ConcurrentDictionary _pinyinCache = new ConcurrentDictionary(); + private Settings _settings; + + public void Initialize([NotNull] Settings settings) + { + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + public string Translate(string content) + { + if (_settings.ShouldUsePinyin) + { + if (!_pinyinCache.ContainsKey(content)) + { + if (WordsHelper.HasChinese(content)) + { + 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(' '); + + bool pre = false; + + for (int i = 0; i < resultList.Length; i++) + { + if (content[i] >= 0x3400 && content[i] <= 0x9FD5) + { + resultBuilder.Append(' '); + resultBuilder.Append(resultList[i]); + pre = true; + } + else + { + if (pre) + { + pre = false; + resultBuilder.Append(' '); + } + resultBuilder.Append(resultList[i]); + } + } + + return _pinyinCache[content] = resultBuilder.ToString(); + } + else + { + return content; + } + } + else + { + return _pinyinCache[content]; + } + } + else + { + return content; + } + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 837fe3b71..832b6fbfa 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -9,10 +9,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings { public class Settings : BaseModel { + private string language = "en"; + public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}"; public string OpenResultModifiers { get; set; } = KeyConstant.Alt; public bool ShowOpenResultHotkey { get; set; } = true; - public string Language { get; set; } = "en"; + public string Language + { + get => language; set { + language = value; + OnPropertyChanged(); + } + } public string Theme { get; set; } = Constant.DefaultTheme; public bool UseDropShadowEffect { get; set; } = false; public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name; diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 0aacc321b..70013c274 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 @@ -14,10 +14,10 @@ - 1.2.0 - 1.2.0 - 1.2.0 - 1.2.0 + 1.3.1 + 1.3.1 + 1.3.1 + 1.3.1 Flow.Launcher.Plugin Flow-Launcher MIT @@ -60,12 +60,9 @@ - + - - - \ No newline at end of file diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index 681973905..ccc00d5e9 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -63,12 +63,6 @@ namespace Flow.Launcher.Plugin /// void OpenSettingDialog(); - /// - /// Install Flow Launcher plugin - /// - /// Plugin path (ends with .flowlauncher) - void InstallPlugin(string path); - /// /// Get translation of current language /// You need to implement IPluginI18n if you want to support multiple languages for your plugin diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 13905788a..27cd1a558 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -17,7 +17,7 @@ namespace Flow.Launcher.Plugin.SharedCommands /// /// /// - public static void Copy(this string sourcePath, string targetPath) + public static void CopyAll(this string sourcePath, string targetPath) { // Get the subdirectories for the specified directory. DirectoryInfo dir = new DirectoryInfo(sourcePath); @@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin.SharedCommands foreach (DirectoryInfo subdir in dirs) { string temppath = Path.Combine(targetPath, subdir.Name); - Copy(subdir.FullName, temppath); + CopyAll(subdir.FullName, temppath); } } catch (Exception e) @@ -114,7 +114,7 @@ namespace Flow.Launcher.Plugin.SharedCommands return Directory.Exists(path); } - public static bool FileExits(this string filePath) + public static bool FileExists(this string filePath) { return File.Exists(filePath); } @@ -124,7 +124,7 @@ namespace Flow.Launcher.Plugin.SharedCommands var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = fileOrFolderPath }; try { - if (LocationExists(fileOrFolderPath) || FileExits(fileOrFolderPath)) + if (LocationExists(fileOrFolderPath) || FileExists(fileOrFolderPath)) Process.Start(psi); } catch (Exception e) diff --git a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs index b23da730e..95d057707 100644 --- a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs +++ b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs @@ -1,3 +1,4 @@ +using Microsoft.Win32; using System; using System.Diagnostics; using System.IO; @@ -7,12 +8,37 @@ namespace Flow.Launcher.Plugin.SharedCommands { public static class SearchWeb { + private static string GetDefaultBrowserPath() + { + string name = string.Empty; + try + { + using var regDefault = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice", false); + var stringDefault = regDefault.GetValue("ProgId"); + + using var regKey = Registry.ClassesRoot.OpenSubKey(stringDefault + "\\shell\\open\\command", false); + name = regKey.GetValue(null).ToString().ToLower().Replace("\"", ""); + + if (!name.EndsWith("exe")) + name = name.Substring(0, name.LastIndexOf(".exe") + 4); + + } + catch + { + return string.Empty; + } + + return name; + } + /// /// Opens search in a new browser. If no browser path is passed in then Chrome is used. /// Leave browser path blank to use Chrome. /// public static void NewBrowserWindow(this string url, string browserPath = "") { + browserPath = string.IsNullOrEmpty(browserPath) ? GetDefaultBrowserPath() : browserPath; + var browserExecutableName = browserPath? .Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None) .Last(); @@ -44,7 +70,9 @@ namespace Flow.Launcher.Plugin.SharedCommands /// public static void NewTabInBrowser(this string url, string browserPath = "") { - var psi = new ProcessStartInfo() { UseShellExecute = true}; + browserPath = string.IsNullOrEmpty(browserPath) ? GetDefaultBrowserPath() : browserPath; + + var psi = new ProcessStartInfo() { UseShellExecute = true }; try { if (!string.IsNullOrEmpty(browserPath)) diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln index 1dd93b2ba..4d8997177 100644 --- a/Flow.Launcher.sln +++ b/Flow.Launcher.sln @@ -15,6 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc ProjectSection(ProjectDependencies) = postProject {1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {1EE20B48-82FB-48A2-8086-675D6DDAB4F0} {0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {0B9DE348-9361-4940-ADB6-F5953BFFCCEC} + {4792A74A-0CEA-4173-A8B2-30E6764C6217} = {4792A74A-0CEA-4173-A8B2-30E6764C6217} {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4} {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E} @@ -23,15 +24,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc {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} - {049490F0-ECD2-4148-9B39-2135EC346EBE} = {049490F0-ECD2-4148-9B39-2135EC346EBE} {403B57F2-1856-4FC7-8A24-36AB346B763E} = {403B57F2-1856-4FC7-8A24-36AB346B763E} {588088F4-3262-4F9F-9663-A05DE12534C3} = {588088F4-3262-4F9F-9663-A05DE12534C3} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Infrastructure", "Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj", "{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.PluginManagement", "Plugins\Flow.Launcher.Plugin.PluginManagement\Flow.Launcher.Plugin.PluginManagement.csproj", "{049490F0-ECD2-4148-9B39-2135EC346EBE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Core", "Flow.Launcher.Core\Flow.Launcher.Core.csproj", "{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Program", "Plugins\Flow.Launcher.Plugin.Program\Flow.Launcher.Plugin.Program.csproj", "{FDB3555B-58EF-4AE6-B5F1-904719637AB4}" @@ -53,6 +51,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitattributes = .gitattributes .gitignore = .gitignore appveyor.yml = appveyor.yml + Directory.Build.targets = Directory.Build.targets Scripts\flowlauncher.nuspec = Scripts\flowlauncher.nuspec LICENSE = LICENSE Scripts\post_build.ps1 = Scripts\post_build.ps1 @@ -70,6 +69,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Explor EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.ProcessKiller", "Plugins\Flow.Launcher.Plugin.ProcessKiller\Flow.Launcher.Plugin.ProcessKiller.csproj", "{588088F4-3262-4F9F-9663-A05DE12534C3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.PluginsManager", "Plugins\Flow.Launcher.Plugin.PluginsManager\Flow.Launcher.Plugin.PluginsManager.csproj", "{4792A74A-0CEA-4173-A8B2-30E6764C6217}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -128,18 +129,6 @@ Global {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x64.Build.0 = Release|Any CPU {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x86.ActiveCfg = Release|Any CPU {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x86.Build.0 = Release|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x64.ActiveCfg = Debug|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x64.Build.0 = Debug|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x86.ActiveCfg = Debug|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x86.Build.0 = Debug|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|Any CPU.Build.0 = Release|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x64.ActiveCfg = Release|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x64.Build.0 = Release|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x86.ActiveCfg = Release|Any CPU - {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x86.Build.0 = Release|Any CPU {B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -297,12 +286,23 @@ Global {588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x64.Build.0 = Release|Any CPU {588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x86.ActiveCfg = Release|Any CPU {588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x86.Build.0 = Release|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x64.ActiveCfg = Debug|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x64.Build.0 = Debug|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x86.ActiveCfg = Debug|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x86.Build.0 = Debug|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|Any CPU.Build.0 = Release|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x64.ActiveCfg = Release|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x64.Build.0 = Release|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x86.ActiveCfg = Release|Any CPU + {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {049490F0-ECD2-4148-9B39-2135EC346EBE} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {403B57F2-1856-4FC7-8A24-36AB346B763E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} @@ -315,6 +315,7 @@ Global {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {588088F4-3262-4F9F-9663-A05DE12534C3} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} + {4792A74A-0CEA-4173-A8B2-30E6764C6217} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F26ACB50-3F6C-4907-B0C9-1ADACC1D0DED} diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 985d4da87..59bdbc896 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Text; using System.Threading.Tasks; using System.Timers; using System.Windows; @@ -28,7 +29,7 @@ namespace Flow.Launcher private SettingWindowViewModel _settingsVM; private readonly Updater _updater = new Updater(Flow.Launcher.Properties.Settings.Default.GithubRepo); private readonly Portable _portable = new Portable(); - private readonly Alphabet _alphabet = new Alphabet(); + private readonly PinyinAlphabet _alphabet = new PinyinAlphabet(); private StringMatcher _stringMatcher; [STAThread] @@ -85,6 +86,8 @@ namespace Flow.Launcher Http.Proxy = _settings.Proxy; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + RegisterExitEvents(); AutoStartup(); diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 987a685ac..8548ba39e 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -72,23 +72,13 @@ - - - - all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - diff --git a/Flow.Launcher/Images/app_missing_img.png b/Flow.Launcher/Images/app_missing_img.png new file mode 100644 index 000000000..11d5466f0 Binary files /dev/null and b/Flow.Launcher/Images/app_missing_img.png differ diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml index a2ff5926d..bf001d507 100644 --- a/Flow.Launcher/Languages/sk.xaml +++ b/Flow.Launcher/Languages/sk.xaml @@ -1,12 +1,12 @@ - Nepodarilo sa registrovať klávesovú skratku {0} Nepodarilo sa spustiť {0} - Neplatný formát súboru pre plugin Flow Launcher - Pri tomto dopyte umiestniť navrchu - Zrušiť umiestnenie navrchu pri tomto dopyte + Neplatný formát súboru pre plugin Flow Launchera + Pri tomto zadaní umiestniť navrchu + Zrušiť umiestnenie navrchu pri tomto zadaní Spustiť dopyt: {0} Posledný čas realizácie: {0} Otvoriť @@ -15,61 +15,71 @@ Ukončiť - Nastavenia Flow Launcher + Nastavenia Flow Launchera Všeobecné Spustiť Flow Launcher po štarte systému Schovať Flow Launcher po strate fokusu Nezobrazovať upozornenia na novú verziu Zapamätať si posledné umiestnenie Jazyk - Posledný dopyt + Posledné vyhľadávanie Ponechať - Označiť posledný dopyt - Prázdne + Označiť + Vymazať Max. výsledkov - Ignorovať klávesové skraty v režime na celú obrazovku + Ignorovať klávesové skratky v režime na celú obrazovku Priečinok s Pythonom Automatická aktualizácia Vybrať Schovať Flow Launcher po spustení - Schovať ikonu v oblasti oznámení + Schovať ikonu z oblasti oznámení + Presnosť vyhľadávania + Použiť Pinyin Plugin Nájsť ďalšie pluginy Zakázať Skratka akcie - Priečinok s pluginmy + Aktuálna akcia skratky: + Nová akcia skratky: + Priečinok s pluginmi Autor - Čas inic.: - Čas dopytu: + Príprava: {0}ms + Čas dopytu: {0}ms Motív Prehliadať viac motívov - Písmo poľa pre dopyt + Ahojte + Písmo vyhľadávacieho poľa Písmo výsledkov Režim okno Nepriehľadnosť + Motív {0} neexistuje, návrat na predvolený motív + Nepodarilo sa nečítať motív {0}, návrat na predvolený motív - Klávesová skratka + Klávesové skratky Klávesová skratka pre Flow Launcher - Otvorte modifikátory výsledkov - Vlastná klávesová skratka pre dopyt + Modifikáčné klávesy na otvorenie výsledkov Zobraziť klávesovú skratku + Vlastná klávesová skratka na vyhľadávanie Odstrániť Upraviť Pridať Vyberte položku, prosím Ste si istý, že chcete odstrániť klávesovú skratku {0} pre plugin? + Tieňový efekt v poli vyhľadávania + Tieňový efekt významne využíva GPU. + Neodporúča sa, ak je výkon počítača obmedzený. HTTP Proxy Povoliť HTTP Proxy HTTP Server Port - Používateľské meno + Použív. meno Heslo Test Proxy Uložiť @@ -86,13 +96,13 @@ Verzia Flow Launcher bol aktivovaný {0}-krát Skontrolovať aktualizácie - Je dostupná nová verzia {0}, prosím, reštartujte Flow Launcher. + Je dostupná nová verzia {0}, chcete reštartovať Flow Launcher, aby sa mohol aktualizovať? Kontrola aktualizácií zlyhala, prosím, skontrolujte pripojenie na internet a nastavenie proxy k api.github.com. Sťahovanie aktualizácií zlyhalo, skontrolujte pripojenie na internet a nastavenie proxy k github-cloud.s3.amazonaws.com, - alebo prejdite na https://github.com/Flow-Launcher/Flow.Launcher/releases pre manuálne stiahnutie aktualizácií. + alebo prejdite na https://github.com/Flow-Launcher/Flow.Launcher/releases pre manuálne stiahnutie aktualizácie. - Poznámky k vydaniu: + Poznámky k vydaniu Stará skratka akcie @@ -131,7 +141,7 @@ Flow Launcher zaznamenal chybu - Je dostupné nové vydanie Flow Launcher {0} + Je dostupná nová verzia Flow Launcher {0} Počas inštalácie aktualizácií došlo k chybe Aktualizovať Zrušiť diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 0cc671ef6..07bb96339 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -22,7 +22,6 @@ Loaded="OnLoaded" Initialized="OnInitialized" Closing="OnClosing" - Drop="OnDrop" LocationChanged="OnLocationChanged" Deactivated="OnDeactivated" PreviewKeyDown="OnKeyDown" diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 2c0beaaab..3812b4e1f 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -52,12 +52,14 @@ namespace Flow.Launcher private void OnInitialized(object sender, EventArgs e) { - // show notify icon when flowlauncher is hided - InitializeNotifyIcon(); + } private void OnLoaded(object sender, RoutedEventArgs _) { + // show notify icon when flowlauncher is hidden + InitializeNotifyIcon(); + // todo is there a way to set blur only once? ThemeManager.Instance.SetBlurForWindow(); WindowsInteropHelper.DisableControlBox(this); @@ -87,11 +89,17 @@ namespace Flow.Launcher }; _settings.PropertyChanged += (o, e) => { - if (e.PropertyName == nameof(Settings.HideNotifyIcon)) + switch (e.PropertyName) { - _notifyIcon.Visible = !_settings.HideNotifyIcon; + case nameof(Settings.HideNotifyIcon): + _notifyIcon.Visible = !_settings.HideNotifyIcon; + break; + case nameof(Settings.Language): + UpdateNotifyIconText(); + break; } }; + InitializePosition(); } @@ -103,6 +111,18 @@ namespace Flow.Launcher _settings.WindowLeft = Left; } + private void UpdateNotifyIconText() + { + var menu = _notifyIcon.ContextMenuStrip; + var open = menu.Items[0]; + var setting = menu.Items[1]; + var exit = menu.Items[2]; + + open.Text = InternationalizationManager.Instance.GetTranslation("iconTrayOpen"); + setting.Text = InternationalizationManager.Instance.GetTranslation("iconTraySettings"); + exit.Text = InternationalizationManager.Instance.GetTranslation("iconTrayExit"); + } + private void InitializeNotifyIcon() { _notifyIcon = new NotifyIcon @@ -179,25 +199,6 @@ namespace Flow.Launcher } } - - private void OnDrop(object sender, DragEventArgs e) - { - if (e.Data.GetDataPresent(DataFormats.FileDrop)) - { - // Note that you can have more than one file. - string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); - if (files[0].ToLower().EndsWith(".flowlauncher")) - { - PluginManager.InstallPlugin(files[0]); - } - else - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("invalidFlowLauncherPluginFileFormat")); - } - } - e.Handled = false; - } - private void OnPreviewDragOver(object sender, DragEventArgs e) { e.Handled = true; @@ -294,7 +295,5 @@ namespace Flow.Launcher _viewModel.QueryTextCursorMovedToEnd = false; } } - - } } \ No newline at end of file diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 23f5d85b7..90d4fff63 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -21,11 +21,11 @@ namespace Flow.Launcher { private readonly SettingWindowViewModel _settingsVM; private readonly MainViewModel _mainVM; - private readonly Alphabet _alphabet; + private readonly PinyinAlphabet _alphabet; #region Constructor - public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, Alphabet alphabet) + public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, PinyinAlphabet alphabet) { _settingsVM = settingsVM; _mainVM = mainVM; @@ -76,7 +76,6 @@ namespace Flow.Launcher _settingsVM.Save(); PluginManager.Save(); ImageLoader.Save(); - _alphabet.Save(); } public void ReloadAllPluginData() @@ -116,11 +115,6 @@ namespace Flow.Launcher _mainVM.ProgressBarVisibility = Visibility.Collapsed; } - public void InstallPlugin(string path) - { - Application.Current.Dispatcher.Invoke(() => PluginManager.InstallPlugin(path)); - } - public string GetTranslation(string key) { return InternationalizationManager.Instance.GetTranslation(key); diff --git a/Flow.Launcher/ReportWindow.xaml.cs b/Flow.Launcher/ReportWindow.xaml.cs index b4e0414ab..7318fe4cd 100644 --- a/Flow.Launcher/ReportWindow.xaml.cs +++ b/Flow.Launcher/ReportWindow.xaml.cs @@ -52,8 +52,8 @@ namespace Flow.Launcher var link = new Hyperlink { IsEnabled = true }; link.Inlines.Add(url); link.NavigateUri = new Uri(url); - link.RequestNavigate += (s, e) => SearchWeb.NewBrowserWindow(e.Uri.ToString()); - link.Click += (s, e) => SearchWeb.NewBrowserWindow(url); + link.RequestNavigate += (s, e) => SearchWeb.NewTabInBrowser(e.Uri.ToString()); + link.Click += (s, e) => SearchWeb.NewTabInBrowser(url); paragraph.Inlines.Add(textBeforeUrl); paragraph.Inlines.Add(link); diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 3280dc457..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/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index f8903e891..32f9e9a6e 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -172,7 +172,7 @@ - + @@ -211,10 +211,10 @@ + + - diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs index eb5fd7de0..e5583da33 100644 --- a/Flow.Launcher/SettingWindow.xaml.cs +++ b/Flow.Launcher/SettingWindow.xaml.cs @@ -8,6 +8,7 @@ using NHotkey; using NHotkey.Wpf; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; +using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -228,7 +229,7 @@ namespace Flow.Launcher var uri = new Uri(website); if (Uri.CheckSchemeName(uri.Scheme)) { - SearchWeb.NewBrowserWindow(website); + SearchWeb.NewTabInBrowser(website); } } } @@ -262,7 +263,7 @@ namespace Flow.Launcher private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) { - SearchWeb.NewBrowserWindow(e.Uri.AbsoluteUri); + SearchWeb.NewTabInBrowser(e.Uri.AbsoluteUri); e.Handled = true; } @@ -275,5 +276,10 @@ namespace Flow.Launcher { Close(); } + + private void OpenPluginFolder(object sender, RoutedEventArgs e) + { + FilesFolders.OpenPath(Path.Combine(DataLocation.DataDirectory(), Constant.Themes)); + } } } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f18b74022..7a3aa9f2f 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -136,7 +136,7 @@ namespace Flow.Launcher.ViewModel StartHelpCommand = new RelayCommand(_ => { - SearchWeb.NewBrowserWindow("https://github.com/Flow-Launcher/Flow.Launcher/wiki/Flow-Launcher/"); + SearchWeb.NewTabInBrowser("https://github.com/Flow-Launcher/Flow.Launcher/wiki/Flow-Launcher/"); }); OpenResultCommand = new RelayCommand(index => diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 4392234eb..00a0e1ae5 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -1,23 +1,66 @@ using System; +using System.Threading.Tasks; using System.Windows; using System.Windows.Media; -using System.Windows.Threading; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; - namespace Flow.Launcher.ViewModel { public class ResultViewModel : BaseModel { + public class LazyAsync : Lazy> + { + private T defaultValue; + + private readonly Action _updateCallback; + public new T Value + { + get + { + if (!IsValueCreated) + { + base.Value.ContinueWith(_ => + { + _updateCallback(); + }); + + return defaultValue; + } + + if (!base.Value.IsCompleted || base.Value.IsFaulted) + return defaultValue; + + 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 LazyAsync( + SetImage, + ImageLoader.DefaultImage, + () => + { + OnPropertyChanged(nameof(Image)); + }); } Settings = settings; @@ -25,39 +68,45 @@ 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 ImageSource Image + public LazyAsync Image { get; set; } + + private async Task 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.ErrorIcon; - } + 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; + } + } + + 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)); + } } public Result Result { get; } @@ -84,6 +133,5 @@ namespace Flow.Launcher.ViewModel { return Result.ToString(); } - } } diff --git a/JsonRPC/wox.py b/JsonRPC/wox.py deleted file mode 100644 index 1beaa1d7e..000000000 --- a/JsonRPC/wox.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function -import json -import sys -import inspect - -class FlowLauncher(object): - """ - Flow.Launcher python plugin base - """ - - def __init__(self): - rpc_request = json.loads(sys.argv[1]) - # proxy is not working now - self.proxy = rpc_request.get("proxy",{}) - request_method_name = rpc_request.get("method") - request_parameters = rpc_request.get("parameters") - methods = inspect.getmembers(self, predicate=inspect.ismethod) - - request_method = dict(methods)[request_method_name] - results = request_method(*request_parameters) - - if request_method_name == "query" or request_method_name == "context_menu": - print(json.dumps({"result": results})) - - def query(self,query): - """ - sub class need to override this method - """ - return [] - - def context_menu(self, data): - """ - optional context menu entries for a result - """ - return [] - - def debug(self,msg): - """ - alert msg - """ - print("DEBUG:{}".format(msg)) - sys.exit() - -class FlowLauncherAPI(object): - - @classmethod - def change_query(cls,query,requery = False): - """ - change flowlauncher query - """ - print(json.dumps({"method": "Flow.Launcher.ChangeQuery","parameters":[query,requery]})) - - @classmethod - def shell_run(cls,cmd): - """ - run shell commands - """ - print(json.dumps({"method": "Flow.Launcher.ShellRun","parameters":[cmd]})) - - @classmethod - def close_app(cls): - """ - close flowlauncher - """ - print(json.dumps({"method": "Flow.Launcher.CloseApp","parameters":[]})) - - @classmethod - def hide_app(cls): - """ - hide flowlauncher - """ - print(json.dumps({"method": "Flow.Launcher.HideApp","parameters":[]})) - - @classmethod - def show_app(cls): - """ - show flowlauncher - """ - print(json.dumps({"method": "Flow.Launcher.ShowApp","parameters":[]})) - - @classmethod - def show_msg(cls,title,sub_title,ico_path=""): - """ - show messagebox - """ - print(json.dumps({"method": "Flow.Launcher.ShowMsg","parameters":[title,sub_title,ico_path]})) - - @classmethod - def open_setting_dialog(cls): - """ - open setting dialog - """ - print(json.dumps({"method": "Flow.Launcher.OpenSettingDialog","parameters":[]})) - - @classmethod - def start_loadingbar(cls): - """ - start loading animation in flowlauncher - """ - print(json.dumps({"method": "Flow.Launcher.StartLoadingBar","parameters":[]})) - - @classmethod - def stop_loadingbar(cls): - """ - stop loading animation in flowlauncher - """ - print(json.dumps({"method": "Flow.Launcher.StopLoadingBar","parameters":[]})) - - @classmethod - def reload_plugins(cls): - """ - reload all flowlauncher plugins - """ - print(json.dumps({"method": "Flow.Launcher.ReloadPlugins","parameters":[]})) diff --git a/LICENSE b/LICENSE index cb4b563c0..8be14c31d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ The MIT License (MIT) +Copyright (c) 2019 Flow-Launcher Copyright (c) 2015 Wox Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Bookmark.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Bookmark.cs index 1149042dd..790c03686 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Bookmark.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Bookmark.cs @@ -19,10 +19,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark set { m_Name = value; - PinyinName = m_Name.Unidecode(); } } - public string PinyinName { get; private set; } public string Url { get; set; } public string Source { get; set; } public int Score { get; set; } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/Bookmarks.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/Bookmarks.cs index 7c2db8bf9..c7013aa67 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/Bookmarks.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/Bookmarks.cs @@ -6,19 +6,19 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Commands { internal static class Bookmarks { - internal static bool MatchProgram(Bookmark bookmark, string queryString) + internal static MatchResult MatchProgram(Bookmark bookmark, string queryString) { - if (StringMatcher.FuzzySearch(queryString, bookmark.Name).IsSearchPrecisionScoreMet()) return true; - if (StringMatcher.FuzzySearch(queryString, bookmark.PinyinName).IsSearchPrecisionScoreMet()) return true; - if (StringMatcher.FuzzySearch(queryString, bookmark.Url).IsSearchPrecisionScoreMet()) return true; + var match = StringMatcher.FuzzySearch(queryString, bookmark.Name); + if (match.IsSearchPrecisionScoreMet()) + return match; - return false; + return StringMatcher.FuzzySearch(queryString, bookmark.Url); } internal static List LoadAllBookmarks() { var allbookmarks = new List(); - + var chromeBookmarks = new ChromeBookmarks(); var mozBookmarks = new FirefoxBookmarks(); var edgeBookmarks = new EdgeBookmarks(); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarks.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarks.cs index 12b80c08a..376808549 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarks.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarks.cs @@ -18,7 +18,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark return bookmarks; } - private void ParseEdgeBookmarks(String path, string source) + private void ParseEdgeBookmarks(string path, string source) { if (!File.Exists(path)) return; @@ -72,12 +72,13 @@ namespace Flow.Launcher.Plugin.BrowserBookmark private void LoadEdgeBookmarks() { - String platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge"); + LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev"); LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary"); } - private String DecodeUnicode(String dataStr) + private string DecodeUnicode(string dataStr) { Regex reg = new Regex(@"(?i)\\[uU]([0-9a-f]{4})"); return reg.Replace(dataStr, m => ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString()); 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 13daddf10..85b745a6b 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -1,11 +1,13 @@  + Library netcoreapp3.1 {9B130CC5-14FB-41FF-B310-0A95B6894C37} Properties Flow.Launcher.Plugin.BrowserBookmark Flow.Launcher.Plugin.BrowserBookmark + true false false diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml new file mode 100644 index 000000000..825fa7737 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml @@ -0,0 +1,15 @@ + + + + Prehliadač záložiek + Vyhľadáva záložky prehliadača + + + Otvoriť záložky v: + Nové okno + Nová karta + Nastaviť cestu k prehliadaču: + Prehliadať + \ 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 67ee87f94..47493654f 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -12,7 +12,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable { private PluginInitContext context; - + private List cachedBookmarks = new List(); private readonly Settings _settings; @@ -37,36 +37,56 @@ namespace Flow.Launcher.Plugin.BrowserBookmark // Should top results be returned? (true if no search parameters have been passed) var topResults = string.IsNullOrEmpty(param); - - var returnList = cachedBookmarks; + if (!topResults) { // Since we mixed chrome and firefox bookmarks, we should order them again - returnList = cachedBookmarks.Where(o => Bookmarks.MatchProgram(o, param)).ToList(); - returnList = returnList.OrderByDescending(o => o.Score).ToList(); - } - - return returnList.Select(c => new Result() - { - Title = c.Name, - SubTitle = c.Url, - IcoPath = @"Images\bookmark.png", - Score = 5, - Action = (e) => + var returnList = cachedBookmarks.Select(c => new Result() { - if (_settings.OpenInNewBrowserWindow) + Title = c.Name, + SubTitle = c.Url, + IcoPath = @"Images\bookmark.png", + Score = Bookmarks.MatchProgram(c, param).Score, + Action = _ => { - c.Url.NewBrowserWindow(_settings.BrowserPath); - } - else - { - c.Url.NewTabInBrowser(_settings.BrowserPath); - } + if (_settings.OpenInNewBrowserWindow) + { + c.Url.NewBrowserWindow(_settings.BrowserPath); + } + else + { + c.Url.NewTabInBrowser(_settings.BrowserPath); + } - return true; - } - }).ToList(); + return true; + } + }).Where(r => r.Score > 0); + return returnList.ToList(); + } + else + { + return cachedBookmarks.Select(c => new Result() + { + Title = c.Name, + SubTitle = c.Url, + IcoPath = @"Images\bookmark.png", + Score = 5, + Action = _ => + { + if (_settings.OpenInNewBrowserWindow) + { + c.Url.NewBrowserWindow(_settings.BrowserPath); + } + else + { + c.Url.NewTabInBrowser(_settings.BrowserPath); + } + + return true; + } + }).ToList(); + } } public void ReloadData() diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json index 8676a3e5b..de4f3849b 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json @@ -4,9 +4,9 @@ "Name": "Browser Bookmarks", "Description": "Search your browser bookmarks", "Author": "qianlifeng, Ioannis G.", - "Version": "1.2.0", + "Version": "1.3.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", - "ExecuteFileName": "Flow.Launcher.Plugin.browserBookmark.dll", + "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", "IcoPath": "Images\\bookmark.png" } 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 e7cae42ae..9e1fefdb3 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -1,12 +1,14 @@ - + + Library netcoreapp3.1 {59BD9891-3837-438A-958D-ADC7F91F6F7E} Properties Flow.Launcher.Plugin.Caculator Flow.Launcher.Plugin.Caculator - true + true + true false false @@ -102,9 +104,7 @@ - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/sk.xaml new file mode 100644 index 000000000..c08f0265c --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/sk.xaml @@ -0,0 +1,16 @@ + + + Kalkulačka + Spracúva matematické operácie.(Skúste 5*3-2 vo flowlauncheri) + Nie je číslo (NaN) + Nesprávny alebo neúplný výraz (Nezabudli ste na zátvorky?) + Kopírovať toto číslo do schránky + Oddeľovač des. miest + Oddeľovač desatinných miest použitý vo výsledku. + Použiť podľa systému + Čiarka (,) + Bodka (.) + Desatinné miesta + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 1235a18e7..949911229 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -34,7 +34,7 @@ namespace Flow.Launcher.Plugin.Caculator { MagesEngine = new Engine(); } - + public void Init(PluginInitContext context) { Context = context; @@ -78,16 +78,16 @@ namespace Flow.Launcher.Plugin.Caculator { try { - Clipboard.SetText(newResult); + Clipboard.SetDataObject(newResult); return true; } - catch (ExternalException) + catch (ExternalException e) { MessageBox.Show("Copy failed, please try later"); return false; } } - } + } }; } } @@ -111,7 +111,7 @@ namespace Flow.Launcher.Plugin.Caculator { return false; } - + if (!IsBracketComplete(query.Search)) { return false; @@ -164,7 +164,7 @@ namespace Flow.Launcher.Plugin.Caculator return leftBracketCount == 0; } - + public string GetTranslatedPluginTitle() { return Context.API.GetTranslation("flowlauncher_plugin_caculator_plugin_name"); diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json index 284a2893d..709757d1a 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.0.0", + "Version": "1.1.3", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Caculator.dll", 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 19f8fb980..c7fe8271a 100644 --- a/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj +++ b/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj @@ -1,11 +1,13 @@  + Library netcoreapp3.1 {F35190AA-4758-4D9E-A193-E3BDF6AD3567} Properties Flow.Launcher.Plugin.Color Flow.Launcher.Plugin.Color + true false false @@ -96,9 +98,4 @@ - - - - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Color/Languages/sk.xaml similarity index 50% rename from Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/en.xaml rename to Plugins/Flow.Launcher.Plugin.Color/Languages/sk.xaml index b49f33c76..4b208691a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Color/Languages/sk.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"> - Plugin Management - Install, remove or update Flow Launcher plugins + 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/plugin.json b/Plugins/Flow.Launcher.Plugin.Color/plugin.json index 938f8f853..8c0c483ba 100644 --- a/Plugins/Flow.Launcher.Plugin.Color/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Color/plugin.json @@ -4,7 +4,7 @@ "Name": "Colors", "Description": "Provide hex color preview.(Try #000 in Flow Launcher)", "Author": "qianlifeng", - "Version": "1.0.0", + "Version": "1.1.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Color.dll", 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 d1c185c36..699737634 100644 --- a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj +++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj @@ -1,11 +1,13 @@  + Library netcoreapp3.1 {1EE20B48-82FB-48A2-8086-675D6DDAB4F0} Properties Flow.Launcher.Plugin.ControlPanel Flow.Launcher.Plugin.ControlPanel + true false false @@ -96,9 +98,4 @@ - - - - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.ControlPanel/Languages/sk.xaml similarity index 50% rename from Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-cn.xaml rename to Plugins/Flow.Launcher.Plugin.ControlPanel/Languages/sk.xaml index 009fd976c..3b9249b78 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/Languages/sk.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"> - Flow Launcher插件管理 - 安装/卸载/更新Flow Launcher插件 - + Ovládací panel + Vyhľadáva položky Ovládacieho panela + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/plugin.json b/Plugins/Flow.Launcher.Plugin.ControlPanel/plugin.json index 9a4b7ee0e..4f552a014 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.0.0", + "Version": "1.1.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.ControlPanel.dll", 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 efa5339b4..a1a08843a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -5,6 +5,7 @@ netcoreapp3.1 true true + true false diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index b7710d332..2fb16e0e1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -9,6 +9,7 @@ Are you sure you want to permanently delete this {0}? Deletion successful Successfully deleted the {0} + Assigning the global action keyword could bring up too many results during search. Please choose a specific action keyword Delete diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index db2eaa722..38939e244 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -22,6 +22,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal const char AllFilesFolderSearchWildcard = '>'; + internal const string DefaultContentSearchActionKeyword = "doc:"; + internal const char DirectorySeperator = '\\'; internal const string WindowsIndexingOptions = "srchadmin.dll"; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs index ebde039d6..8bd19956e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs @@ -6,14 +6,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks { public class QuickFolderAccess { - internal List FolderList(Query query, List folderLinks, PluginInitContext context) + internal List FolderListMatched(Query query, List folderLinks, PluginInitContext context) { if (string.IsNullOrEmpty(query.Search)) - return folderLinks - .Select(item => - new ResultManager(context) - .CreateFolderResult(item.Nickname, item.Path, item.Path, query)) - .ToList(); + return new List(); string search = query.Search.ToLower(); @@ -24,5 +20,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks .CreateFolderResult(item.Nickname, item.Path, item.Path, query)) .ToList(); } + + internal List FolderListAll(Query query, List folderLinks, PluginInitContext context) + => folderLinks + .Select(item => + new ResultManager(context).CreateFolderResult(item.Nickname, item.Path, item.Path, query)) + .ToList(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 6c0d186f1..5b50b7fad 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -34,17 +34,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search var querySearch = query.Search; - var quickFolderLinks = quickFolderAccess.FolderList(query, settings.QuickFolderAccessLinks, context); - - if (quickFolderLinks.Count > 0 && query.ActionKeyword == settings.SearchActionKeyword) - return quickFolderLinks; - - if (string.IsNullOrEmpty(querySearch)) - return results; - if (IsFileContentSearch(query.ActionKeyword)) return WindowsIndexFileContentSearch(query, querySearch); + // 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); + + var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context); + + if (quickFolderLinks.Count > 0) + results.AddRange(quickFolderLinks); + var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch); if (isEnvironmentVariable) @@ -54,7 +57,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search var isEnvironmentVariablePath = querySearch.Substring(1).Contains("%\\"); if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath) - return WindowsIndexFilesAndFoldersSearch(query, querySearch); + { + results.AddRange(WindowsIndexFilesAndFoldersSearch(query, querySearch)); + + return results; + } var locationPath = querySearch; @@ -137,15 +144,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search private bool UseWindowsIndexForDirectorySearch(string locationPath) { + var pathToDirectory = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath); + if (!settings.UseWindowsIndexForDirectorySearch) return false; if (settings.IndexSearchExcludedSubdirectoryPaths - .Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath) + .Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory) .StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))) return false; - return indexSearch.PathIsIndexed(locationPath); + return indexSearch.PathIsIndexed(pathToDirectory); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs index 08511091e..4f9325c77 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs @@ -21,7 +21,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) { @@ -51,7 +51,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) { - var path = new Uri(dataReaderResults.GetString(1)).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); + + var path = new Uri(encodedFragmentPath).LocalPath; if (dataReaderResults.GetString(2) == "Directory") { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 4e62b3cba..5b12870c8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,4 +1,5 @@ -using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; +using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using Newtonsoft.Json; using System.Collections.Generic; @@ -22,6 +23,6 @@ namespace Flow.Launcher.Plugin.Explorer public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign; [JsonProperty] - public string FileContentSearchActionKeyword { get; set; } = "doc:"; + public string FileContentSearchActionKeyword { get; set; } = Constants.DefaultContentSearchActionKeyword; } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index e14a6ebb5..7fcd77f07 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -54,5 +54,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); + + internal bool IsNewActionKeywordGlobal(string newActionKeyword) => newActionKeyword == Query.GlobalPluginWildcardSign; } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs index b9e5373b7..2957283ad 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs @@ -51,8 +51,17 @@ namespace Flow.Launcher.Plugin.Explorer.Views return; } + + if (settingsViewModel.IsNewActionKeywordGlobal(newActionKeyword) + && currentActionKeyword.Description + == settingsViewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_filecontentsearch")) + { + MessageBox.Show(settingsViewModel.Context.API.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); + + return; + } - if(!settingsViewModel.IsActionKeywordAlreadyAssigned(newActionKeyword)) + if (!settingsViewModel.IsActionKeywordAlreadyAssigned(newActionKeyword)) { settingsViewModel.UpdateActionKeyword(newActionKeyword, currentActionKeyword.Keyword); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index 67d2e731c..2c57ac668 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.2", + "Version": "1.2.5", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", 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 48639156e..e6bfa7aa3 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -1,11 +1,13 @@  + Library netcoreapp3.1 {FDED22C8-B637-42E8-824A-63B5B6E05A3A} Properties Flow.Launcher.Plugin.PluginIndicator Flow.Launcher.Plugin.PluginIndicator + true false false @@ -96,10 +98,5 @@ PreserveNewest - - - - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/sk.xaml new file mode 100644 index 000000000..9cb04a907 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/sk.xaml @@ -0,0 +1,8 @@ + + + Plugin Indicator + Ponúka návrhy pre akcie pluginov + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json index 0c1cce6eb..80900a445 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.0.0", + "Version": "1.1.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj b/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj deleted file mode 100644 index 49451d5ba..000000000 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj +++ /dev/null @@ -1,106 +0,0 @@ - - - - netcoreapp3.1 - {049490F0-ECD2-4148-9B39-2135EC346EBE} - Properties - Flow.Launcher.Plugin.PluginManagement - Flow.Launcher.Plugin.PluginManagement - true - false - false - - - - true - full - false - ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.PluginManagement\ - DEBUG;TRACE - prompt - 4 - false - - - - pdbonly - true - ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.PluginManagement\ - 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.PluginManagement/Flow.Launcher.PluginResult.cs b/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.PluginResult.cs deleted file mode 100644 index 7f5d75d4e..000000000 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.PluginResult.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Flow.Launcher.Plugin.PluginManagement -{ - public class FlowLauncherPluginResult - { - public string plugin_file; - public string description; - public int liked_count; - public string name; - public string version; - } -} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Images/plugin.png b/Plugins/Flow.Launcher.Plugin.PluginManagement/Images/plugin.png deleted file mode 100644 index 6ff9b8b15..000000000 Binary files a/Plugins/Flow.Launcher.Plugin.PluginManagement/Images/plugin.png and /dev/null differ diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/de.xaml deleted file mode 100644 index 38b2f1b4b..000000000 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/de.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Flow Launcher Plugin Verwaltung - Installiere/Entferne/Aktualisiere Flow Launcher Plugins - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/pl.xaml deleted file mode 100644 index 362db73e5..000000000 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/pl.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Zarządzanie wtyczkami Flow Launcher - Pozwala na instalacje, usuwanie i aktualizacje wtyczek - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/tr.xaml deleted file mode 100644 index fee82a78b..000000000 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/tr.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Flow Launcher Eklenti Yöneticisi - Flow Launcher eklentilerini kurun, kaldırın ya da güncelleyin - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-tw.xaml deleted file mode 100644 index c93d740f1..000000000 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-tw.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - Flow Launcher 外掛管理 - 安裝/解除安裝/更新 Flow Launcher 外掛 - - diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs deleted file mode 100644 index 0255216a0..000000000 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs +++ /dev/null @@ -1,260 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using Newtonsoft.Json; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.Http; -using Flow.Launcher.Infrastructure.Logger; - -namespace Flow.Launcher.Plugin.PluginManagement -{ - public class Main : IPlugin, IPluginI18n - { - private static string APIBASE = "http://api.wox.one"; - private static string pluginSearchUrl = APIBASE + "/plugin/search/"; - private const string ListCommand = "list"; - private const string InstallCommand = "install"; - private const string UninstallCommand = "uninstall"; - private PluginInitContext context; - - public List Query(Query query) - { - List results = new List(); - - if (string.IsNullOrEmpty(query.Search)) - { - results.Add(ResultForListCommandAutoComplete(query)); - results.Add(ResultForInstallCommandAutoComplete(query)); - results.Add(ResultForUninstallCommandAutoComplete(query)); - return results; - } - - string command = query.FirstSearch.ToLower(); - if (string.IsNullOrEmpty(command)) return results; - - if (command == ListCommand) - { - return ResultForListInstalledPlugins(); - } - if (command == UninstallCommand) - { - return ResultForUnInstallPlugin(query); - } - if (command == InstallCommand) - { - return ResultForInstallPlugin(query); - } - - if (InstallCommand.Contains(command)) - { - results.Add(ResultForInstallCommandAutoComplete(query)); - } - if (UninstallCommand.Contains(command)) - { - results.Add(ResultForUninstallCommandAutoComplete(query)); - } - if (ListCommand.Contains(command)) - { - results.Add(ResultForListCommandAutoComplete(query)); - } - - return results; - } - - private Result ResultForListCommandAutoComplete(Query query) - { - string title = ListCommand; - string subtitle = "list installed plugins"; - return ResultForCommand(query, ListCommand, title, subtitle); - } - - private Result ResultForInstallCommandAutoComplete(Query query) - { - string title = $"{InstallCommand} "; - string subtitle = "list installed plugins"; - return ResultForCommand(query, InstallCommand, title, subtitle); - } - - private Result ResultForUninstallCommandAutoComplete(Query query) - { - string title = $"{UninstallCommand} "; - string subtitle = "list installed plugins"; - return ResultForCommand(query, UninstallCommand, title, subtitle); - } - - private Result ResultForCommand(Query query, string command, string title, string subtitle) - { - const string seperater = Plugin.Query.TermSeperater; - var result = new Result - { - Title = title, - IcoPath = "Images\\plugin.png", - SubTitle = subtitle, - Action = e => - { - context.API.ChangeQuery($"{query.ActionKeyword}{seperater}{command}{seperater}"); - return false; - } - }; - return result; - } - - private List ResultForInstallPlugin(Query query) - { - List results = new List(); - string pluginName = query.SecondSearch; - if (string.IsNullOrEmpty(pluginName)) return results; - string json; - try - { - json = Http.Get(pluginSearchUrl + pluginName).Result; - } - catch (WebException e) - { - //todo happlebao add option in log to decide give user prompt or not - context.API.ShowMsg("PluginManagement.ResultForInstallPlugin: Can't connect to Wox plugin website, check your conenction"); - Log.Exception("|PluginManagement.ResultForInstallPlugin|Can't connect to Wox plugin website, check your conenction", e); - return new List(); - } - List searchedPlugins; - try - { - searchedPlugins = JsonConvert.DeserializeObject>(json); - } - catch (JsonSerializationException e) - { - context.API.ShowMsg("PluginManagement.ResultForInstallPlugin: Coundn't parse api search results, Please update your Flow Launcher!"); - Log.Exception("|PluginManagement.ResultForInstallPlugin|Coundn't parse api search results, Please update your Flow Launcher!", e); - return results; - } - - foreach (FlowLauncherPluginResult r in searchedPlugins) - { - FlowLauncherPluginResult r1 = r; - results.Add(new Result - { - Title = r.name, - SubTitle = r.description, - IcoPath = "Images\\plugin.png", - TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.name).MatchData, - SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.description).MatchData, - Action = _ => - { - MessageBoxResult result = MessageBox.Show("Are you sure you wish to install the \'" + r.name + "\' plugin", - "Install plugin", MessageBoxButton.YesNo); - - if (result == MessageBoxResult.Yes) - { - string folder = Path.Combine(Path.GetTempPath(), "FlowLauncherPluginDownload"); - if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); - string filePath = Path.Combine(folder, Guid.NewGuid().ToString() + ".flowlauncher"); - - string pluginUrl = APIBASE + "/media/" + r1.plugin_file; - - Task.Run(async () => - { - 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; - } - }); - } - return results; - } - - private List ResultForUnInstallPlugin(Query query) - { - List results = new List(); - List allInstalledPlugins = context.API.GetAllPlugins().Select(o => o.Metadata).ToList(); - if (!string.IsNullOrEmpty(query.SecondSearch)) - { - allInstalledPlugins = - allInstalledPlugins.Where(o => o.Name.ToLower().Contains(query.SecondSearch.ToLower())).ToList(); - } - - foreach (PluginMetadata plugin in allInstalledPlugins) - { - results.Add(new Result - { - Title = plugin.Name, - SubTitle = plugin.Description, - IcoPath = plugin.IcoPath, - TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Name).MatchData, - SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Description).MatchData, - Action = e => - { - UnInstallPlugin(plugin); - return false; - } - }); - } - return results; - } - - private void UnInstallPlugin(PluginMetadata plugin) - { - string content = $"Do you want to uninstall following plugin?{Environment.NewLine}{Environment.NewLine}" + - $"Name: {plugin.Name}{Environment.NewLine}" + - $"Version: {plugin.Version}{Environment.NewLine}" + - $"Author: {plugin.Author}"; - if (MessageBox.Show(content, "Flow Launcher", MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - File.Create(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")).Close(); - var result = MessageBox.Show($"You have uninstalled plugin {plugin.Name} successfully.{Environment.NewLine}" + - "Restart Flow Launcher to take effect?", - "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question); - if (result == MessageBoxResult.Yes) - { - context.API.RestartApp(); - } - } - } - - private List ResultForListInstalledPlugins() - { - List results = new List(); - foreach (PluginMetadata plugin in context.API.GetAllPlugins().Select(o => o.Metadata)) - { - string actionKeywordString = string.Join(" or ", plugin.ActionKeywords.ToArray()); - results.Add(new Result - { - Title = $"{plugin.Name} - Action Keywords: {actionKeywordString}", - SubTitle = plugin.Description, - IcoPath = plugin.IcoPath - }); - } - return results; - } - - public void Init(PluginInitContext context) - { - this.context = context; - } - - public string GetTranslatedPluginTitle() - { - return context.API.GetTranslation("flowlauncher_plugin_plugin_management_plugin_name"); - } - - public string GetTranslatedPluginDescription() - { - return context.API.GetTranslation("flowlauncher_plugin_plugin_management_plugin_description"); - } - } -} diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginManagement/plugin.json deleted file mode 100644 index 5a8259f8f..000000000 --- a/Plugins/Flow.Launcher.Plugin.PluginManagement/plugin.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ID": "D2D2C23B084D422DB66FE0C79D6C2A6A", - "ActionKeyword": "wpm", - "Name": "Plugin Management", - "Description": "Install/Remove/Update Flow Launcher plugins", - "Author": "qianlifeng", - "Version": "1.0", - "Language": "csharp", - "Website": "https://github.com/Flow-Launcher/Flow.Launcher", - "ExecuteFileName": "Flow.Launcher.Plugin.PluginManagement.dll", - "IcoPath": "Images\\plugin.png" -} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs new file mode 100644 index 000000000..76cb0f86b --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -0,0 +1,79 @@ +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin.PluginsManager.Models; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Flow.Launcher.Plugin.PluginsManager +{ + internal class ContextMenu : IContextMenu + { + private PluginInitContext Context { get; set; } + + private Settings Settings { get; set; } + + public ContextMenu(PluginInitContext context, Settings settings) + { + Context = context; + Settings = settings; + } + + public List LoadContextMenus(Result selectedResult) + { + 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.NewTabInBrowser(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.NewTabInBrowser(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/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj new file mode 100644 index 000000000..cc1a931ce --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -0,0 +1,46 @@ + + + + Library + netcoreapp3.1 + true + true + true + false + + + + ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.PluginsManager + + + + ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.PluginsManager + + + + + + + + + + PreserveNewest + + + + + + PreserveNewest + + + + + + PreserveNewest + + + + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/pluginsmanager.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/pluginsmanager.png new file mode 100644 index 000000000..65f0e41dc Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/pluginsmanager.png differ 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 000000000..a9126cb9b Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/request.png differ 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 000000000..8efbdaa48 Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/sourcecode.png differ diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/website.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/website.png new file mode 100644 index 000000000..f96ba15b2 Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/website.png differ diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml new file mode 100644 index 000000000..8d24c145c --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -0,0 +1,37 @@ + + + + Downloading plugin + Please wait... + Successfully downloaded + {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 + 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 + This plugin has an update, would you like to see it? + This plugin is already installed + + + + + Plugins Manager + 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/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs new file mode 100644 index 000000000..43f92e7b9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -0,0 +1,70 @@ +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; +using System.Windows.Controls; + +namespace Flow.Launcher.Plugin.PluginsManager +{ + public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n + { + internal PluginInitContext Context { get; set; } + + internal Settings Settings; + + private SettingsViewModel viewModel; + + private IContextMenu contextMenu; + + public Control CreateSettingPanel() + { + return new PluginsManagerSettings(viewModel); + } + + public void Init(PluginInitContext context) + { + Context = context; + viewModel = new SettingsViewModel(context); + Settings = viewModel.Settings; + contextMenu = new ContextMenu(Context, Settings); + } + + public List LoadContextMenus(Result selectedResult) + { + return contextMenu.LoadContextMenus(selectedResult); + } + + public List Query(Query query) + { + var search = query.Search.ToLower(); + + 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); + } + + public void Save() + { + viewModel.Save(); + } + + public string GetTranslatedPluginTitle() + { + return Context.API.GetTranslation("plugin_pluginsmanager_plugin_name"); + } + + public string GetTranslatedPluginDescription() + { + return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs new file mode 100644 index 000000000..290221710 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs @@ -0,0 +1,40 @@ +using Flow.Launcher.Infrastructure.Http; +using Flow.Launcher.Infrastructure.Logger; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.PluginsManager.Models +{ + internal class PluginsManifest + { + internal List UserPlugins { get; private set; } + internal PluginsManifest() + { + DownloadManifest(); + } + + private void 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(); + + UserPlugins = JsonConvert.DeserializeObject>(json); + } + catch (Exception e) + { + Log.Exception("|PluginManagement.GetManifest|Encountered error trying to download plugins manifest", e); + + UserPlugins = new List(); + } + + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs new file mode 100644 index 000000000..c1af3014b --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs @@ -0,0 +1,16 @@ + +namespace Flow.Launcher.Plugin.PluginsManager.Models +{ + public class UserPlugin + { + public string ID { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Author { get; set; } + public string Version { get; set; } + public string Language { get; set; } + public string Website { get; set; } + public string UrlDownload { get; set; } + public string UrlSourceCode { get; set; } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs new file mode 100644 index 000000000..90f3277fb --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -0,0 +1,332 @@ +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure.Http; +using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin.PluginsManager.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows; + +namespace Flow.Launcher.Plugin.PluginsManager +{ + internal class PluginsManager + { + private readonly PluginsManifest pluginsManifest; + + private PluginInitContext Context { get; set; } + + 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) + { + pluginsManifest = new PluginsManifest(); + 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 (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; + } + + var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"), + plugin.Name, plugin.Author, + Environment.NewLine, Environment.NewLine); + + 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"); + + try + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), + 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")); + } + catch (Exception e) + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), + 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(); }); + } + + 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; + } + }); + + return Search(results, uninstallSearch); + } + + internal bool PluginExists(string id) + { + return Context.API.GetAllPlugins().Any(x => x.Metadata.ID == id); + } + + internal List Search(IEnumerable results, string searchName) + { + if (string.IsNullOrEmpty(searchName)) + return results.ToList(); + + return results + .Where(x => + { + var matchResult = StringMatcher.FuzzySearch(searchName, x.Title); + if (matchResult.IsSearchPrecisionScoreMet()) + x.Score = matchResult.Score; + + 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 => + { + Application.Current.MainWindow.Hide(); + InstallOrUpdate(x); + + return ShouldHideWindow; + }, + ContextData = x + }); + + return Search(results, searchName); + } + + private void Install(UserPlugin plugin, string downloadedFilePath) + { + if (!File.Exists(downloadedFilePath)) + return; + + var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher"); + var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin"); + + if (Directory.Exists(tempFolderPath)) + Directory.Delete(tempFolderPath, true); + + Directory.CreateDirectory(tempFolderPath); + + var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath)); + + File.Move(downloadedFilePath, zipFilePath); + + Utilities.UnZip(zipFilePath, tempFolderPluginPath, true); + + var pluginFolderPath = Utilities.GetContainingFolderPathAfterUnzip(tempFolderPluginPath); + + var metadataJsonFilePath = string.Empty; + if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName))) + metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName); + + if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) + { + MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile")); + return; + } + + string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}"); + + Directory.Move(pluginFolderPath, newPluginPath); + } + + internal List RequestUninstall(string search) + { + var autocompletedResults = AutoCompleteReturnAllResults(search, + Settings.HotkeyUninstall, + "Uninstall", + "Select a plugin to uninstall"); + + if (autocompletedResults.Any()) + return autocompletedResults; + + 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); + + 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; + } + }); + + return Search(results, uninstallSearch); + } + + private void Uninstall(PluginMetadata plugin) + { + // 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) + { + 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(); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs new file mode 100644 index 000000000..e2e8d22e5 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Flow.Launcher.Plugin.PluginsManager +{ + internal class Settings + { + internal string HotkeyUninstall { get; set; } = "uninstall"; + + internal string HotkeyUpdate { get; set; } = "update"; + } +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs new file mode 100644 index 000000000..2853ffc9e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs @@ -0,0 +1,61 @@ +using Flow.Launcher.Infrastructure.Http; +using ICSharpCode.SharpZipLib.Zip; +using System.IO; +using System.Net; + +namespace Flow.Launcher.Plugin.PluginsManager +{ + internal static class Utilities + { + /// + /// Unzip contents to the given directory. + /// + /// The path to the zip file. + /// The output directory. + /// overwrite + internal static void UnZip(string zipFilePath, string strDirectory, bool overwrite) + { + if (strDirectory == "") + strDirectory = Directory.GetCurrentDirectory(); + + using var zipStream = new ZipInputStream(File.OpenRead(zipFilePath)); + + ZipEntry theEntry; + + while ((theEntry = zipStream.GetNextEntry()) != null) + { + var pathToZip = theEntry.Name; + var directoryName = string.IsNullOrEmpty(pathToZip) ? "" : Path.GetDirectoryName(pathToZip); + var fileName = Path.GetFileName(pathToZip); + var destinationDir = Path.Combine(strDirectory, directoryName); + var destinationFile = Path.Combine(destinationDir, fileName); + + Directory.CreateDirectory(destinationDir); + + if (string.IsNullOrEmpty(fileName) || (File.Exists(destinationFile) && !overwrite)) + continue; + + using var streamWriter = File.Create(destinationFile); + zipStream.CopyTo(streamWriter); + } + } + + internal static string GetContainingFolderPathAfterUnzip(string unzippedParentFolderPath) + { + var unzippedFolderCount = Directory.GetDirectories(unzippedParentFolderPath).Length; + var unzippedFilesCount = Directory.GetFiles(unzippedParentFolderPath).Length; + + // adjust path depending on how the plugin is zipped up + // the recommended should be to zip up the folder not the contents + if (unzippedFolderCount == 1 && unzippedFilesCount == 0) + // folder is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/pluginFolderName/ + return Directory.GetDirectories(unzippedParentFolderPath)[0]; + + if (unzippedFilesCount > 1) + // content is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/ + return unzippedParentFolderPath; + + return string.Empty; + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs new file mode 100644 index 000000000..f3cf117d3 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs @@ -0,0 +1,26 @@ +using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Infrastructure.UserSettings; + +namespace Flow.Launcher.Plugin.PluginsManager.ViewModels +{ + public class SettingsViewModel + { + private readonly PluginJsonStorage storage; + + internal Settings Settings { get; set; } + + internal PluginInitContext Context { get; set; } + + public SettingsViewModel(PluginInitContext context) + { + Context = context; + storage = new PluginJsonStorage(); + Settings = storage.Load(); + } + + public void Save() + { + storage.Save(); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml new file mode 100644 index 000000000..89d27f6ff --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml.cs new file mode 100644 index 000000000..14204eda9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml.cs @@ -0,0 +1,22 @@ + +using Flow.Launcher.Plugin.PluginsManager.ViewModels; + +namespace Flow.Launcher.Plugin.PluginsManager.Views +{ + /// + /// Interaction logic for PluginsManagerSettings.xaml + /// + public partial class PluginsManagerSettings + { + private readonly SettingsViewModel viewModel; + + public PluginsManagerSettings(SettingsViewModel viewModel) + { + InitializeComponent(); + + this.viewModel = viewModel; + + //RefreshView(); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json new file mode 100644 index 000000000..e970e5a8e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -0,0 +1,14 @@ +{ + "ID": "9f8f9b14-2518-4907-b211-35ab6290dee7", + "ActionKeywords": [ + "pm" + ], + "Name": "Plugins Manager", + "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", + "Author": "Jeremy Wu", + "Version": "1.3.0", + "Language": "csharp", + "Website": "https://github.com/Flow-Launcher/Flow.Launcher", + "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", + "IcoPath": "Images\\pluginsmanager.png" +} 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 ab84aa54a..cf9c96294 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -1,6 +1,7 @@  + Library netcoreapp3.1 Flow.Launcher.Plugin.ProcessKiller Flow.Launcher.Plugin.ProcessKiller @@ -8,6 +9,7 @@ https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller flow-launcher flow-plugin + true false false diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml index 2eee31745..e7a136114 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml @@ -5,7 +5,8 @@ Process Killer Kill running processes from Flow Launcher - kill all "{0}" processes + kill all instances of "{0}" + kill {0} processes kill all instances \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sk.xaml new file mode 100644 index 000000000..e185dbe71 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sk.xaml @@ -0,0 +1,12 @@ + + + Process Killer + Ukončuje spustené procesy z Flow Launchera + + ukončiť všetky inštancie "{0}" + ukončiť {0} procesov + ukončiť všetky inštancie + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs index 22c84b20f..c3d9d1ab2 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs @@ -52,21 +52,24 @@ namespace Flow.Launcher.Plugin.ProcessKiller // get all non-system processes whose file path matches that of the given result (processPath) var similarProcesses = processHelper.GetSimilarProcesses(processPath); - menuOptions.Add(new Result + if (similarProcesses.Count() > 0) { - Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"), - SubTitle = processPath, - Action = _ => + menuOptions.Add(new Result { - foreach (var p in similarProcesses) + Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"), + SubTitle = processPath, + Action = _ => { - processHelper.TryKill(p); - } + foreach (var p in similarProcesses) + { + processHelper.TryKill(p); + } - return true; - }, - IcoPath = processPath - }); + return true; + }, + IcoPath = processPath + }); + } return menuOptions; } @@ -86,6 +89,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller SubTitle = path, TitleHighlightData = StringMatcher.FuzzySearch(termToSearch, p.ProcessName).MatchData, Score = pr.Score, + ContextData = p.ProcessName, Action = (c) => { processHelper.TryKill(p); @@ -94,16 +98,18 @@ namespace Flow.Launcher.Plugin.ProcessKiller }); } + var sortedResults = results.OrderBy(x => x.Title).ToList(); + // When there are multiple results AND all of them are instances of the same executable // add a quick option to kill them all at the top of the results. - var firstResult = results.FirstOrDefault()?.SubTitle; - if (processlist.Count > 1 && !string.IsNullOrEmpty(termToSearch) && results.All(r => r.SubTitle == firstResult)) + var firstResult = sortedResults.FirstOrDefault(x => !string.IsNullOrEmpty(x.SubTitle)); + if (processlist.Count > 1 && !string.IsNullOrEmpty(termToSearch) && sortedResults.All(r => r.SubTitle == firstResult?.SubTitle)) { - results.Insert(0, new Result() + sortedResults.Insert(1, new Result() { - IcoPath = "Images/app.png", - Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), termToSearch), - SubTitle = "", + IcoPath = firstResult?.IcoPath, + Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), firstResult?.ContextData), + SubTitle = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all_count"), processlist.Count), Score = 200, Action = (c) => { @@ -117,7 +123,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller }); } - return results; + return sortedResults; } } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json b/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json index f2e1eca12..d769397a8 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.0.0", + "Version":"1.2.1", "Language":"csharp", "Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller", "IcoPath":"Images\\app.png", 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..3802297c7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -1,6 +1,7 @@  + Library netcoreapp3.1 {FDB3555B-58EF-4AE6-B5F1-904719637AB4} Properties @@ -8,6 +9,7 @@ Flow.Launcher.Plugin.Program true true + true false false @@ -107,10 +109,7 @@ - - - diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml index 579a64718..c266aa6f6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml @@ -40,7 +40,12 @@ Search programs in Flow Launcher Invalid Path - + + Customized Explorer + Args + You can customized the explorer used for opening the container folder by inputing the Environmental Variable of the explorer you want to use. It will be useful to use CMD to test whether the Environmental Variable is avaliable. + Enter the customized args you want to add for your customized explorer. %s for parent directory, %f for full path (which only works for win32). Check the explorer's website for details. + Success Successfully disabled this program from displaying in your query diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml new file mode 100644 index 000000000..851233407 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml @@ -0,0 +1,53 @@ + + + + Odstrániť + Upraviť + Pridať + Zakázať + Umiestnenie + Všetky programy + Prípony súborov + Reindexovať + Indexovanie + Indexovať Ponuku Štart + Indexovať Registry + Prípony + Max. hĺbka + + Priečinok: + Prehliadať + Prípony súboru: + Max. hĺbka hľadania (-1 je neobm.): + + Prosím, zadajte zdroj programu + Naozaj chcete odstrániť vybrané zdroje programov? + + Aktualizovať + Flow Launcher bude indexovať iba súbory s nasledujúcimi príponami: + (Každú príponu oddeľte ;) + Prípony boli úspešne aktualizované + Súbor s príponami nemôže byť prázdny + + Spustiť ako iný používateľ + Spustiť ako správca + Otvoriť umiestnenie súboru + Zakázať zobrazovanie tohto programu + + Program + Vyhľadávanie programov vo Flow Launcheri + + Neplatná cesta + + Vlastný správca súborov + Arg. + Môžete si prispôsobiť otváranie umiestnenia priečinka vložením Premenných prostredia, ktoré chcete použiť. Dostupnosť premenných prostredia môžete vyskúšať cez príkazový riadok. + Zadajte argumenty, ktoré chcete pridať pre správcu súborov. %s pre rodičovský priečinok, %f pre celú cestu (funguje iba pre win32). Pre podrobnosti pozrite webovú stránku správcu súborov. + + + Úspešné + Úspešne zakázané zobrazovanie tohto programu vo výsledkoch vyhľadávania + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Logger/ProgramLogger.cs b/Plugins/Flow.Launcher.Plugin.Program/Logger/ProgramLogger.cs index 3c8ad783f..06264c06c 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Logger/ProgramLogger.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Logger/ProgramLogger.cs @@ -98,17 +98,17 @@ namespace Flow.Launcher.Plugin.Program.Logger internal static void LogException(string message, Exception e) { //Index 0 is always empty. - var parts = message.Split('|'); + var parts = message.Split('|', StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 4) { var logger = LogManager.GetLogger(""); logger.Error(e, $"fail to log exception in program logger, parts length is too small: {parts.Length}, message: {message}"); } - var classname = parts[1]; - var callingMethodName = parts[2]; - var loadingProgramPath = parts[3]; - var interpretationMessage = parts[4]; + var classname = parts[0]; + var callingMethodName = parts[1]; + var loadingProgramPath = parts[2]; + var interpretationMessage = parts[3]; LogException(classname, callingMethodName, loadingProgramPath, interpretationMessage, e); } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 9f3160746..8f124f3a4 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -71,21 +71,17 @@ namespace Flow.Launcher.Plugin.Program Win32[] win32; UWP.Application[] uwps; - lock (IndexLock) - { // just take the reference inside the lock to eliminate query time issues. - win32 = _win32s; - uwps = _uwps; - } + win32 = _win32s; + uwps = _uwps; - var results1 = win32.AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)); + 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(); - var results2 = uwps.AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)); - - var result = results1.Concat(results2).Where(r => r != null && r.Score > 0).ToList(); return result; } @@ -97,10 +93,9 @@ namespace Flow.Launcher.Plugin.Program public static void IndexWin32Programs() { var win32S = Win32.All(_settings); - lock (IndexLock) - { - _win32s = win32S; - } + + _win32s = win32S; + } public static void IndexUWPPrograms() @@ -109,10 +104,9 @@ namespace Flow.Launcher.Plugin.Program var support = Environment.OSVersion.Version.Major >= windows10.Major; var applications = support ? UWP.All() : new UWP.Application[] { }; - lock (IndexLock) - { - _uwps = applications; - } + + _uwps = applications; + } public static void IndexPrograms() diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/IProgram.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/IProgram.cs index b42acfbce..d4c96e5b7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/IProgram.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/IProgram.cs @@ -9,5 +9,6 @@ namespace Flow.Launcher.Plugin.Program.Programs string UniqueIdentifier { get; set; } string Name { get; } string Location { get; } + bool Enabled { get; } } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index b8633f357..3ea78156d 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -265,27 +265,30 @@ namespace Flow.Launcher.Plugin.Program.Programs public Application(){} - private int Score(string query) - { - var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName); - var descriptionMatch = StringMatcher.FuzzySearch(query, Description); - var score = new[] { displayNameMatch.Score, descriptionMatch.Score }.Max(); - return score; - } public Result Result(string query, IPublicAPI api) { - var score = Score(query); - if (score <= 0) - { // no need to create result if score is 0 + var title = (Name, Description) switch + { + (var n, null) => n, + (var n, var d) when d.StartsWith(n) => d, + (var n, var d) when n.StartsWith(d) => n, + (var n, var d) when !string.IsNullOrEmpty(d) => $"{n}: {d}", + _ => Name + }; + + var matchResult = StringMatcher.FuzzySearch(query, title); + + if (!matchResult.Success) return null; - } var result = new Result { + Title = title, SubTitle = Package.Location, Icon = Logo, - Score = score, + Score = matchResult.Score, + TitleHighlightData = matchResult.MatchData, ContextData = this, Action = e => { @@ -294,23 +297,7 @@ namespace Flow.Launcher.Plugin.Program.Programs } }; - if (Description.Length >= DisplayName.Length && - Description.Substring(0, DisplayName.Length) == DisplayName) - { - result.Title = Description; - result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData; - } - else if (!string.IsNullOrEmpty(Description)) - { - var title = $"{DisplayName}: {Description}"; - result.Title = title; - result.TitleHighlightData = StringMatcher.FuzzySearch(query, title).MatchData; - } - else - { - result.Title = DisplayName; - result.TitleHighlightData = StringMatcher.FuzzySearch(query, DisplayName).MatchData; - } + return result; } @@ -324,7 +311,14 @@ namespace Flow.Launcher.Plugin.Program.Programs Action = _ => { - Main.StartProcess(Process.Start, new ProcessStartInfo(Package.Location)); + Main.StartProcess(Process.Start, + new ProcessStartInfo( + !string.IsNullOrEmpty(Main._settings.CustomizedExplorer) + ? Main._settings.CustomizedExplorer + : Settings.Explorer, + Main._settings.CustomizedArgs + .Replace("%s",$"\"{Package.Location}\"") + .Trim())); return true; }, @@ -536,7 +530,7 @@ namespace Flow.Launcher.Plugin.Program.Programs ProgramLogger.LogException($"|UWP|ImageFromPath|{path}" + $"|Unable to get logo for {UserModelId} from {path} and" + $" located in {Package.Location}", new FileNotFoundException()); - return new BitmapImage(new Uri(Constant.ErrorIcon)); + return new BitmapImage(new Uri(Constant.MissingImgIcon)); } } @@ -586,7 +580,7 @@ namespace Flow.Launcher.Plugin.Program.Programs $"|Unable to convert background string {BackgroundColor} " + $"to color for {Package.Location}", new InvalidOperationException()); - return new BitmapImage(new Uri(Constant.ErrorIcon)); + return new BitmapImage(new Uri(Constant.MissingImgIcon)); } } else diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index cdea767f3..77278330a 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -33,29 +33,30 @@ namespace Flow.Launcher.Plugin.Program.Programs private const string ApplicationReferenceExtension = "appref-ms"; private const string ExeExtension = "exe"; - private int Score(string query) - { - var nameMatch = StringMatcher.FuzzySearch(query, Name); - var descriptionMatch = StringMatcher.FuzzySearch(query, Description); - var executableNameMatch = StringMatcher.FuzzySearch(query, ExecutableName); - var score = new[] { nameMatch.Score, descriptionMatch.Score, executableNameMatch.Score }.Max(); - return score; - } - public Result Result(string query, IPublicAPI api) { - var score = Score(query); - if (score <= 0) - { // no need to create result if this is zero + var title = (Name, Description) switch + { + (var n, null) => n, + (var n, var d) when d.StartsWith(n) => d, + (var n, var d) when n.StartsWith(d) => n, + (var n, var d) when !string.IsNullOrEmpty(d) => $"{n}: {d}", + _ => Name + }; + + var matchResult = StringMatcher.FuzzySearch(query, title); + + if (!matchResult.Success) return null; - } var result = new Result { + Title = title, SubTitle = FullPath, IcoPath = IcoPath, - Score = score, + Score = matchResult.Score, + TitleHighlightData = matchResult.MatchData, ContextData = this, Action = e => { @@ -72,24 +73,6 @@ namespace Flow.Launcher.Plugin.Program.Programs } }; - if (Description.Length >= Name.Length && - Description.Substring(0, Name.Length) == Name) - { - result.Title = Description; - result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData; - } - else if (!string.IsNullOrEmpty(Description)) - { - var title = $"{Name}: {Description}"; - result.Title = title; - result.TitleHighlightData = StringMatcher.FuzzySearch(query, title).MatchData; - } - else - { - result.Title = Name; - result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData; - } - return result; } @@ -140,9 +123,20 @@ namespace Flow.Launcher.Plugin.Program.Programs Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), 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.StartProcess(Process.Start, new ProcessStartInfo("explorer", ParentDirectory)); + Main.StartProcess(Process.Start, + new ProcessStartInfo( + !string.IsNullOrWhiteSpace(Main._settings.CustomizedExplorer) + ? Main._settings.CustomizedExplorer + : Settings.Explorer, + args)); return true; }, @@ -255,9 +249,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var program = Win32Program(path); var info = FileVersionInfo.GetVersionInfo(path); if (!string.IsNullOrEmpty(info.FileDescription)) - { program.Description = info.FileDescription; - } return program; } catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) @@ -273,47 +265,27 @@ namespace Flow.Launcher.Plugin.Program.Programs { if (!Directory.Exists(directory)) return new string[] { }; - var files = new List(); - var folderQueue = new Queue(); - folderQueue.Enqueue(directory); - do + try { - var currentDirectory = folderQueue.Dequeue(); - try - { - foreach (var suffix in suffixes) - { - try - { - files.AddRange(Directory.EnumerateFiles(currentDirectory, $"*.{suffix}", SearchOption.TopDirectoryOnly)); - } - catch (DirectoryNotFoundException e) - { - ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" + - "|The directory trying to load the program from does not exist", e); - } - } - } - catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) - { - ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" + - $"|Permission denied when trying to load programs from {currentDirectory}", e); - } + var paths = Directory.EnumerateFiles(directory, "*", new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = true + }) + .Where(x => suffixes.Contains(Extension(x))); - try - { - foreach (var childDirectory in Directory.EnumerateDirectories(currentDirectory, "*", SearchOption.TopDirectoryOnly)) - { - folderQueue.Enqueue(childDirectory); - } - } - catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) - { - ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" + - $"|Permission denied when trying to load programs from {currentDirectory}", e); - } - } while (folderQueue.Any()); - return files; + return paths; + } + catch (DirectoryNotFoundException e) + { + 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[] { }; + } } private static string Extension(string path) @@ -331,23 +303,20 @@ namespace Flow.Launcher.Plugin.Program.Programs private static ParallelQuery UnregisteredPrograms(List sources, string[] suffixes) { - var listToAdd = new List(); - sources.Where(s => Directory.Exists(s.Location) && s.Enabled) + var paths = sources.Where(s => Directory.Exists(s.Location) && s.Enabled) .SelectMany(s => ProgramPaths(s.Location, suffixes)) - .ToList() .Where(t1 => !Main._settings.DisabledProgramSources.Any(x => t1 == x.UniqueIdentifier)) - .ToList() - .ForEach(x => listToAdd.Add(x)); + .Distinct(); - var paths = listToAdd.Distinct().ToArray(); + var programs = paths.AsParallel().Select(x => Extension(x) switch + { + ExeExtension => ExeProgram(x), + ShortcutExtension => LnkProgram(x), + _ => Win32Program(x) + }); - var programs1 = paths.AsParallel().Where(p => Extension(p) == ExeExtension).Select(ExeProgram); - var programs2 = paths.AsParallel().Where(p => Extension(p) == ShortcutExtension).Select(LnkProgram); - var programs3 = from p in paths.AsParallel() - let e = Extension(p) - where e != ShortcutExtension && e != ExeExtension - select Win32Program(p); - return programs1.Concat(programs2).Concat(programs3); + + return programs; } private static ParallelQuery StartMenuPrograms(string[] suffixes) @@ -360,15 +329,16 @@ namespace Flow.Launcher.Plugin.Program.Programs var paths2 = ProgramPaths(directory2, suffixes); var toFilter = paths1.Concat(paths2); - var paths = toFilter - .Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1)) - .Select(t1 => t1) - .Distinct() - .ToArray(); - var programs1 = paths.AsParallel().Where(p => Extension(p) == ShortcutExtension).Select(LnkProgram); - var programs2 = paths.AsParallel().Where(p => Extension(p) == ApplicationReferenceExtension).Select(Win32Program); - var programs = programs1.Concat(programs2).Where(p => p.Valid); + var programs = toFilter + .AsParallel() + .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; } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Settings.cs b/Plugins/Flow.Launcher.Plugin.Program/Settings.cs index fcb4cbf2d..7cb02a852 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Settings.cs @@ -14,9 +14,15 @@ namespace Flow.Launcher.Plugin.Program public bool EnableStartMenuSource { get; set; } = true; public bool EnableRegistrySource { get; set; } = true; + public string CustomizedExplorer { get; set; } = Explorer; + public string CustomizedArgs { get; set; } = ExplorerArgs; internal const char SuffixSeperator = ';'; + internal const string Explorer = "explorer"; + + internal const string ExplorerArgs = "%s"; + /// /// Contains user added folder location contents as well as all user disabled applications /// diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml index 6051e0579..90fda1756 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml @@ -5,12 +5,13 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:program="clr-namespace:Flow.Launcher.Plugin.Program" mc:Ignorable="d" - d:DesignHeight="300" d:DesignWidth="600"> + d:DesignHeight="404.508" d:DesignWidth="600"> + @@ -24,7 +25,7 @@ - + @@ -71,9 +72,18 @@