diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 189a6669e..b0cc07e1a 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -53,6 +53,7 @@ + diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index fcf178445..2c75f9633 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.Loader; using System.Threading.Tasks; using System.Windows.Forms; +using Droplex; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Core.Plugin { @@ -22,7 +23,7 @@ namespace Flow.Launcher.Core.Plugin public static List Plugins(List metadatas, PluginsSettings settings) { var dotnetPlugins = DotNetPlugins(metadatas); - var pythonPlugins = PythonPlugins(metadatas, settings.PythonDirectory); + var pythonPlugins = PythonPlugins(metadatas, settings); var executablePlugins = ExecutablePlugins(metadatas); var plugins = dotnetPlugins.Concat(pythonPlugins).Concat(executablePlugins).ToList(); return plugins; @@ -113,11 +114,14 @@ namespace Flow.Launcher.Core.Plugin return plugins; } - public static IEnumerable PythonPlugins(List source, string pythonDirectory) + public static IEnumerable PythonPlugins(List source, PluginsSettings settings) { - // try to set Constant.PythonPath, either from + if (!source.Any(o => o.Language.ToUpper() == AllowedLanguage.Python)) + return new List(); + + // Try setting Constant.PythonPath first, either from // PATH or from the given pythonDirectory - if (string.IsNullOrEmpty(pythonDirectory)) + if (string.IsNullOrEmpty(settings.PythonDirectory)) { var paths = Environment.GetEnvironmentVariable(PATH); if (paths != null) @@ -129,47 +133,103 @@ namespace Flow.Launcher.Core.Plugin if (pythonInPath) { - Constant.PythonPath = PythonExecutable; + Constant.PythonPath = + Path.Combine(paths.Split(';').Where(p => p.ToLower().Contains(Python)).FirstOrDefault(), PythonExecutable); + settings.PythonDirectory = FilesFolders.GetPreviousExistingDirectory(FilesFolders.LocationExists, Constant.PythonPath); } else { - Log.Error("|PluginsLoader.PythonPlugins|Python can't be found in PATH."); + Log.Error("PluginsLoader","Failed to set Python path despite the environment variable PATH is found", "PythonPlugins"); } } - else - { - Log.Error("|PluginsLoader.PythonPlugins|PATH environment variable is not set."); - } } else { - var path = Path.Combine(pythonDirectory, PythonExecutable); + var path = Path.Combine(settings.PythonDirectory, PythonExecutable); if (File.Exists(path)) { Constant.PythonPath = path; } else { - Log.Error($"|PluginsLoader.PythonPlugins|Can't find python executable in {path}"); + Log.Error("PluginsLoader",$"Tried to automatically set from Settings.PythonDirectory " + + $"but can't find python executable in {path}", "PythonPlugins"); } } - // if we have a path to the python executable, - // load every python plugin pair. - if (String.IsNullOrEmpty(Constant.PythonPath)) + if (string.IsNullOrEmpty(settings.PythonDirectory)) { + if (MessageBox.Show("Flow detected you have installed Python plugins, " + + "would you like to install Python to run them? " + + Environment.NewLine + Environment.NewLine + + "Click no if it's already installed, " + + "and you will be prompted to select the folder that contains the Python executable", + string.Empty, MessageBoxButtons.YesNo) == DialogResult.No + && string.IsNullOrEmpty(settings.PythonDirectory)) + { + var dlg = new FolderBrowserDialog + { + SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + }; + + var result = dlg.ShowDialog(); + if (result == DialogResult.OK) + { + string pythonDirectory = dlg.SelectedPath; + if (!string.IsNullOrEmpty(pythonDirectory)) + { + var pythonPath = Path.Combine(pythonDirectory, PythonExecutable); + if (File.Exists(pythonPath)) + { + settings.PythonDirectory = pythonDirectory; + Constant.PythonPath = pythonPath; + } + else + { + MessageBox.Show("Can't find python in given directory"); + } + } + } + } + else + { + DroplexPackage.Drop(App.python3_9_1).Wait(); + + var installedPythonDirectory = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python39"); + var pythonPath = Path.Combine(installedPythonDirectory, PythonExecutable); + if (FilesFolders.FileExists(pythonPath)) + { + settings.PythonDirectory = installedPythonDirectory; + Constant.PythonPath = pythonPath; + } + else + { + Log.Error("PluginsLoader", + $"Failed to set Python path after Droplex install, {pythonPath} does not exist", + "PythonPlugins"); + } + } + } + + if (string.IsNullOrEmpty(settings.PythonDirectory)) + { + MessageBox.Show("Unable to set Python executable path, please try from Flow's settings (scroll down to the bottom)."); + Log.Error("PluginsLoader", + $"Not able to successfully set Python path, the PythonDirectory variable is still an empty string.", + "PythonPlugins"); + return new List(); } - else - { - return source - .Where(o => o.Language.ToUpper() == AllowedLanguage.Python) - .Select(metadata => new PluginPair - { - Plugin = new PythonPlugin(Constant.PythonPath), - Metadata = metadata - }); - } + + return source + .Where(o => o.Language.ToUpper() == AllowedLanguage.Python) + .Select(metadata => new PluginPair + { + Plugin = new PythonPlugin(Constant.PythonPath), + Metadata = metadata + }) + .ToList(); } public static IEnumerable ExecutablePlugins(IEnumerable source) diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index de2e82359..3a3e770a5 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -75,7 +75,7 @@ namespace Flow.Launcher.Infrastructure.Http { try { - using var response = await client.GetAsync(url, token); + using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); if (response.StatusCode == HttpStatusCode.OK) { await using var fileStream = new FileStream(filePath, FileMode.CreateNew); @@ -135,7 +135,7 @@ namespace Flow.Launcher.Infrastructure.Http public static async Task GetStreamAsync([NotNull] string url, CancellationToken token = default) { Log.Debug($"|Http.Get|Url <{url}>"); - var response = await client.GetAsync(url, token); + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); return await response.Content.ReadAsStreamAsync(); } } diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index f0e4a79fc..37cfec252 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -12,7 +12,7 @@ namespace Flow.Launcher.Infrastructure.Storage public class JsonStrorage where T : new() { private readonly JsonSerializerOptions _serializerSettings; - private T _data; + protected T _data; // need a new directory name public const string DirectoryName = "Settings"; public const string FileSuffix = ".json"; diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index 5418e1837..ca7c454c4 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -15,5 +15,10 @@ namespace Flow.Launcher.Infrastructure.Storage FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); } + + public PluginJsonStorage(T data) : this() + { + _data = data; + } } } diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs index dd73eb0e5..07e2b2700 100644 --- a/Flow.Launcher.Plugin/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/IPublicAPI.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -88,17 +89,86 @@ namespace Flow.Launcher.Plugin /// event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; + /// + /// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses + /// + /// Query string + /// The string that will be compared against the query + /// Match results MatchResult FuzzySearch(string query, string stringToCompare); + /// + /// Http download the spefic url and return as string + /// + /// URL to call Http Get + /// Cancellation Token + /// Task to get string result Task HttpGetStringAsync(string url, CancellationToken token = default); + /// + /// Http download the spefic url and return as stream + /// + /// URL to call Http Get + /// Cancellation Token + /// Task to get stream result Task HttpGetStreamAsync(string url, CancellationToken token = default); - Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath); + /// + /// Download the specific url to a cretain file path + /// + /// URL to download file + /// place to store file + /// Task showing the progress + Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default); + /// + /// Add ActionKeyword for specific plugin + /// + /// ID for plugin that needs to add action keyword + /// The actionkeyword that is supposed to be added void AddActionKeyword(string pluginId, string newActionKeyword); + /// + /// Remove ActionKeyword for specific plugin + /// + /// ID for plugin that needs to remove action keyword + /// The actionkeyword that is supposed to be removed void RemoveActionKeyword(string pluginId, string oldActionKeyword); + /// + /// Log debug message + /// Message will only be logged in Debug mode + /// + void LogDebug(string className, string message, [CallerMemberName] string methodName = ""); + + /// + /// Log info message + /// + void LogInfo(string className, string message, [CallerMemberName] string methodName = ""); + + /// + /// Log warning message + /// + void LogWarn(string className, string message, [CallerMemberName] string methodName = ""); + + /// + /// Log an Exception. Will throw if in debug mode so developer will be aware, + /// otherwise logs the eror message. This is the primary logging method used for Flow + /// + void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = ""); + + /// + /// Load JsonStorage for current plugin. This is the method used to load settings from json in Flow + /// + /// Type for deserialization + /// + T LoadJsonStorage() where T : new(); + + /// + /// Save JsonStorage for current plugin. This is the method used to save settings to json in Flow + /// + /// Type for Serialization + /// + void SaveJsonStorage(T setting) where T : new(); } } diff --git a/Flow.Launcher.Infrastructure/Storage/ISavable.cs b/Flow.Launcher.Plugin/Interfaces/ISavable.cs similarity index 81% rename from Flow.Launcher.Infrastructure/Storage/ISavable.cs rename to Flow.Launcher.Plugin/Interfaces/ISavable.cs index 8294a3df8..7c1110e0e 100644 --- a/Flow.Launcher.Infrastructure/Storage/ISavable.cs +++ b/Flow.Launcher.Plugin/Interfaces/ISavable.cs @@ -1,4 +1,4 @@ -namespace Flow.Launcher.Infrastructure.Storage +namespace Flow.Launcher.Plugin { /// /// Save plugin settings/cache, diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 289a502d0..20e847f82 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -66,6 +66,9 @@ PreserveNewest + + PreserveNewest + diff --git a/Flow.Launcher/Images/app.ico b/Flow.Launcher/Images/app.ico new file mode 100644 index 000000000..36b1d22d0 Binary files /dev/null and b/Flow.Launcher/Images/app.ico differ diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index b6bf76b7f..a036949fc 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -121,6 +121,7 @@ New Action Keyword can't be empty This new Action Keyword is already assigned to another plugin, please choose a different one Success + Completed successfully Use * if you don't want to specify an action keyword diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 4cc0b4428..aa0240dd4 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -34,6 +34,7 @@ + diff --git a/Flow.Launcher/Msg.xaml.cs b/Flow.Launcher/Msg.xaml.cs index 4129ce28b..6bb2fc2dc 100644 --- a/Flow.Launcher/Msg.xaml.cs +++ b/Flow.Launcher/Msg.xaml.cs @@ -66,7 +66,7 @@ namespace Flow.Launcher } if (!File.Exists(iconPath)) { - imgIco.Source = ImageLoader.Load(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\app.png")); + imgIco.Source = ImageLoader.Load(Path.Combine(Constant.ProgramDirectory, "Images\\app.png")); } else { imgIco.Source = ImageLoader.Load(iconPath); diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 427fd9fc6..9b41c2c05 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -18,6 +18,9 @@ using System.Threading; using System.IO; using Flow.Launcher.Infrastructure.Http; using JetBrains.Annotations; +using System.Runtime.CompilerServices; +using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.Storage; namespace Flow.Launcher { @@ -61,18 +64,15 @@ namespace Flow.Launcher // which will cause ungraceful exit SaveAppAllSettings(); + // Restart requires Squirrel's Update.exe to be present in the parent folder, + // it is only published from the project's release pipeline. When debugging without it, + // the project may not restart or just terminates. This is expected. UpdateManager.RestartApp(Constant.ApplicationFileName); } - public void RestarApp() - { - RestartApp(); - } + public void RestarApp() => RestartApp(); - public void CheckForNewUpdate() - { - _settingsVM.UpdateApp(); - } + public void CheckForNewUpdate() => _settingsVM.UpdateApp(); public void SaveAppAllSettings() { @@ -82,15 +82,9 @@ namespace Flow.Launcher ImageLoader.Save(); } - public Task ReloadAllPluginData() - { - return PluginManager.ReloadData(); - } + public Task ReloadAllPluginData() => PluginManager.ReloadData(); - public void ShowMsg(string title, string subTitle = "", string iconPath = "") - { - ShowMsg(title, subTitle, iconPath, true); - } + public void ShowMsg(string title, string subTitle = "", string iconPath = "") => ShowMsg(title, subTitle, iconPath, true); public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true) { @@ -109,54 +103,40 @@ namespace Flow.Launcher }); } - public void StartLoadingBar() - { - _mainVM.ProgressBarVisibility = Visibility.Visible; - } + public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible; - public void StopLoadingBar() - { - _mainVM.ProgressBarVisibility = Visibility.Collapsed; - } + public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed; - public string GetTranslation(string key) - { - return InternationalizationManager.Instance.GetTranslation(key); - } + public string GetTranslation(string key) => InternationalizationManager.Instance.GetTranslation(key); - public List GetAllPlugins() - { - return PluginManager.AllPlugins.ToList(); - } - - public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; + public List GetAllPlugins() => PluginManager.AllPlugins.ToList(); public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); - public Task HttpGetStringAsync(string url, CancellationToken token = default) - { - return Http.GetAsync(url); - } + public Task HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url); - public Task HttpGetStreamAsync(string url, CancellationToken token = default) - { - return Http.GetStreamAsync(url); - } + public Task HttpGetStreamAsync(string url, CancellationToken token = default) => Http.GetStreamAsync(url); - public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath) - { - return Http.DownloadAsync(url, filePath); - } + public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default) => Http.DownloadAsync(url, filePath, token); - public void AddActionKeyword(string pluginId, string newActionKeyword) - { - PluginManager.AddActionKeyword(pluginId, newActionKeyword); - } + public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword); + + public void RemoveActionKeyword(string pluginId, string oldActionKeyword) => PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); + + public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") => Log.Debug(className, message, methodName); + + public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") => Log.Info(className, message, methodName); + + public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") => Log.Warn(className, message, methodName); + + public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName); + + public T LoadJsonStorage() where T : new() => new PluginJsonStorage().Load(); + + public void SaveJsonStorage(T setting) where T : new() => new PluginJsonStorage(setting).Save(); + + public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; - public void RemoveActionKeyword(string pluginId, string oldActionKeyword) - { - PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); - } #endregion #region Private Methods diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 4c7eac114..cb0ef2def 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -11,7 +11,7 @@ xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" x:Class="Flow.Launcher.SettingWindow" mc:Ignorable="d" - Icon="Images\app.png" + Icon="Images\app.ico" Title="{DynamicResource flowlauncher_settings}" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index afbe6e197..a12912b67 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -89,7 +89,6 @@ namespace Flow.Launcher.ViewModel _resultsViewUpdateTask = Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); - async Task updateAction() { var queue = new Dictionary(); @@ -105,9 +104,7 @@ namespace Flow.Launcher.ViewModel UpdateResultView(queue.Values); } - } - - ; + }; void continueAction(Task t) { @@ -115,8 +112,8 @@ namespace Flow.Launcher.ViewModel throw t.Exception; #else Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}"); - _resultsViewUpdateTask = - Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); + _resultsViewUpdateTask = + Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); #endif } } @@ -225,6 +222,25 @@ namespace Flow.Launcher.ViewModel SelectedResults = Results; } }); + + ReloadPluginDataCommand = new RelayCommand(_ => + { + var msg = new Msg { Owner = Application.Current.MainWindow }; + + MainWindowVisibility = Visibility.Collapsed; + + PluginManager + .ReloadData() + .ContinueWith(_ => + Application.Current.Dispatcher.Invoke(() => + { + msg.Show( + InternationalizationManager.Instance.GetTranslation("success"), + InternationalizationManager.Instance.GetTranslation("completedSuccessfully"), + ""); + })) + .ConfigureAwait(false); + }); } #endregion @@ -313,6 +329,7 @@ namespace Flow.Launcher.ViewModel public ICommand LoadContextMenuCommand { get; set; } public ICommand LoadHistoryCommand { get; set; } public ICommand OpenResultCommand { get; set; } + public ICommand ReloadPluginDataCommand { get; set; } public string OpenResultCommandModifiers { get; private set; } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png new file mode 100644 index 000000000..0dee870b6 Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png differ diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml index 3beccb5e7..f456e7495 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml @@ -12,4 +12,6 @@ New tab Set browser from path: Choose + Copy url + Copy the bookmark's url to clipboard \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index 47493654f..b889bb0d0 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -1,6 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Windows; using System.Windows.Controls; +using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.BrowserBookmark.Commands; using Flow.Launcher.Plugin.BrowserBookmark.Models; @@ -9,7 +12,7 @@ using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.BrowserBookmark { - public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable + public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable, IContextMenu { private PluginInitContext context; @@ -60,7 +63,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark } return true; - } + }, + ContextData = new BookmarkAttributes { Url = c.Url } }).Where(r => r.Score > 0); return returnList.ToList(); } @@ -84,7 +88,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark } return true; - } + }, + ContextData = new BookmarkAttributes { Url = c.Url } }).ToList(); } } @@ -115,5 +120,39 @@ namespace Flow.Launcher.Plugin.BrowserBookmark { _storage.Save(); } + + public List LoadContextMenus(Result selectedResult) + { + return new List() { + new Result + { + Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), + SubTitle = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"), + Action = _ => + { + try + { + Clipboard.SetDataObject(((BookmarkAttributes)selectedResult.ContextData).Url); + + return true; + } + catch (Exception e) + { + var message = "Failed to set url in clipboard"; + Log.Exception("Main",message, e, "LoadContextMenus"); + + context.API.ShowMsg(message); + + return false; + } + }, + IcoPath = "Images\\copylink.png" + }}; + } + + internal class BookmarkAttributes + { + internal string Url { get; set; } + } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json index b0c3d2e29..62311f514 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser Bookmarks", "Description": "Search your browser bookmarks", "Author": "qianlifeng, Ioannis G.", - "Version": "1.3.2", + "Version": "1.4.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs index 7bc357be4..85f918a03 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -25,7 +25,7 @@ namespace Flow.Launcher.Plugin.PluginsManager { Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_subtitle"), - IcoPath = "Images\\website.png", + IcoPath = selectedResult.IcoPath, Action = _ => { SharedCommands.SearchWeb.NewTabInBrowser(pluginManifestInfo.Website); @@ -63,7 +63,7 @@ namespace Flow.Launcher.Plugin.PluginsManager { Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"), - IcoPath = selectedResult.IcoPath, + IcoPath = "Images\\manifestsite.png", Action = _ => { SharedCommands.SearchWeb.NewTabInBrowser("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"); diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index cb2507a2b..f94027bcc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -18,6 +18,7 @@ + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/website.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/manifestsite.png similarity index 100% rename from Plugins/Flow.Launcher.Plugin.PluginsManager/Images/website.png rename to Plugins/Flow.Launcher.Plugin.PluginsManager/Images/manifestsite.png diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 881872fc1..90d103bc7 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -1,3 +1,4 @@ +using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; @@ -400,6 +401,9 @@ namespace Flow.Launcher.Plugin.PluginsManager private void Uninstall(PluginMetadata plugin) { + PluginManager.Settings.Plugins.Remove(plugin.ID); + PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + // Marked for deletion. Will be deleted on next start up using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index b37a6b1af..4cba7ec83 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.6.2", + "Version": "1.6.4", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", diff --git a/README.md b/README.md index 9af47a5ee..8512142b0 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,11 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be Windows may complain about security due to code not being signed, this will be completed at a later stage. If you downloaded from this repo, you are good to continue the set up. **Integrations** - - If you use Python plugins: - - Install [Python3](https://www.python.org/downloads/), download `.exe` installer. + - Python plugins: + - Once a Python plugin has been detected at start up, you will be prompted to either select the location or allow Python 3.9.1 to automatic download and install. - Add Python to `%PATH%` or set it in flow's settings. - - Use `pip` to install `flowlauncher`, cmd in `pip install flowlauncher`. + - Use `pip` to install `flowlauncher`, open cmd and type `pip install flowlauncher`. + - The Python plugin may require additional modules to be installed, please ensure you check by visiting the plugin's website via `pm install` + plugin name, go to context menu and select `Open website`. - Start to launch your Python plugins. - Flow searches files and contents via Windows Index Search, to use Everything, download the plugin [here](https://github.com/Flow-Launcher/Flow.Launcher.Plugin.Everything/releases/latest). @@ -51,6 +52,7 @@ Windows may complain about security due to code not being signed, this will be c - Open context menu: on the selected result, press Ctrl+O/Shift+Enter. - Cancel/Return to previous screen: Esc. - Install/Uninstall/Update plugins: in the search window, type `pm install`/`pm uninstall`/`pm update` + the plugin name. +- Press `F5` while in the query window to reload all plugin data. - Saved user settings are located: - If using roaming: `%APPDATA%\FlowLauncher` - If using portable, by default: `%localappdata%\FlowLauncher\app-\UserData` diff --git a/SolutionAssemblyInfo.cs b/SolutionAssemblyInfo.cs index afd76b5d7..7f13f4130 100644 --- a/SolutionAssemblyInfo.cs +++ b/SolutionAssemblyInfo.cs @@ -16,6 +16,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("1.7.0")] -[assembly: AssemblyFileVersion("1.7.0")] -[assembly: AssemblyInformationalVersion("1.7.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.7.1")] +[assembly: AssemblyFileVersion("1.7.1")] +[assembly: AssemblyInformationalVersion("1.7.1")] \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 2c2f43b66..6340318d4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.7.0.{build}' +version: '1.7.1.{build}' init: - ps: |