mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #823 from Garulf/install-from-url
Detect URL and install using Plugin Manager
This commit is contained in:
commit
4f5eb2b454
13 changed files with 294 additions and 53 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
@ -45,8 +45,56 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allPluginMetadata;
|
(List<PluginMetadata> uniqueList, List<PluginMetadata> duplicateList) = GetUniqueLatestPluginMetadata(allPluginMetadata);
|
||||||
|
|
||||||
|
duplicateList
|
||||||
|
.ForEach(
|
||||||
|
x => Log.Warn("PluginConfig",
|
||||||
|
string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
|
||||||
|
"not loaded due to version not the highest of the duplicates",
|
||||||
|
x.Name, x.ID, x.Version),
|
||||||
|
"GetUniqueLatestPluginMetadata"));
|
||||||
|
|
||||||
|
return uniqueList;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static (List<PluginMetadata>, List<PluginMetadata>) GetUniqueLatestPluginMetadata(List<PluginMetadata> allPluginMetadata)
|
||||||
|
{
|
||||||
|
var duplicate_list = new List<PluginMetadata>();
|
||||||
|
var unique_list = new List<PluginMetadata>();
|
||||||
|
|
||||||
|
var duplicateGroups = allPluginMetadata.GroupBy(x => x.ID).Where(g => g.Count() > 1).Select(y => y).ToList();
|
||||||
|
|
||||||
|
foreach (var metadata in allPluginMetadata)
|
||||||
|
{
|
||||||
|
var duplicatesExist = false;
|
||||||
|
foreach (var group in duplicateGroups)
|
||||||
|
{
|
||||||
|
if (metadata.ID == group.Key)
|
||||||
|
{
|
||||||
|
duplicatesExist = true;
|
||||||
|
|
||||||
|
// If metadata's version greater than each duplicate's version, CompareTo > 0
|
||||||
|
var count = group.Where(x => metadata.Version.CompareTo(x.Version) > 0).Count();
|
||||||
|
|
||||||
|
// Only add if the meatadata's version is the highest of all duplicates in the group
|
||||||
|
if (count == group.Count() - 1)
|
||||||
|
{
|
||||||
|
unique_list.Add(metadata);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
duplicate_list.Add(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!duplicatesExist)
|
||||||
|
unique_list.Add(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (unique_list, duplicate_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PluginMetadata GetPluginMetadata(string pluginDirectory)
|
private static PluginMetadata GetPluginMetadata(string pluginDirectory)
|
||||||
|
|
|
||||||
92
Flow.Launcher.Test/PluginLoadTest.cs
Normal file
92
Flow.Launcher.Test/PluginLoadTest.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Flow.Launcher.Core.Plugin;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Test
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
class PluginLoadTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void GivenDuplicatePluginMetadatasWhenLoadedThenShouldReturnOnlyUniqueList()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var duplicateList = new List<PluginMetadata>
|
||||||
|
{
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
|
Version = "1.0.0"
|
||||||
|
},
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
|
Version = "1.0.1"
|
||||||
|
},
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
|
Version = "1.0.2"
|
||||||
|
},
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
|
Version = "1.0.0"
|
||||||
|
},
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
|
Version = "1.0.0"
|
||||||
|
},
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "ABC0TYUC6D3B7855823D60DC76F28855",
|
||||||
|
Version = "1.0.0"
|
||||||
|
},
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "ABC0TYUC6D3B7855823D60DC76F28855",
|
||||||
|
Version = "1.0.0"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// When
|
||||||
|
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
Assert.True(unique.FirstOrDefault().ID == "CEA0TYUC6D3B4085823D60DC76F28855" && unique.FirstOrDefault().Version == "1.0.2");
|
||||||
|
Assert.True(unique.Count() == 1);
|
||||||
|
|
||||||
|
Assert.False(duplicates.Any(x => x.Version == "1.0.2" && x.ID == "CEA0TYUC6D3B4085823D60DC76F28855"));
|
||||||
|
Assert.True(duplicates.Count() == 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GivenDuplicatePluginMetadatasWithNoUniquePluginWhenLoadedThenShouldReturnEmptyList()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var duplicateList = new List<PluginMetadata>
|
||||||
|
{
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "CEA0TYUC6D3B7855823D60DC76F28855",
|
||||||
|
Version = "1.0.0"
|
||||||
|
},
|
||||||
|
new PluginMetadata
|
||||||
|
{
|
||||||
|
ID = "CEA0TYUC6D3B7855823D60DC76F28855",
|
||||||
|
Version = "1.0.0"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// When
|
||||||
|
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
Assert.True(unique.Count() == 0);
|
||||||
|
Assert.True(duplicates.Count() == 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
using NUnit.Framework;
|
|
||||||
using Flow.Launcher.Core.Plugin;
|
|
||||||
using Flow.Launcher.Infrastructure.Exception;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Test.Plugins
|
|
||||||
{
|
|
||||||
|
|
||||||
[TestFixture]
|
|
||||||
public class PluginInitTest
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void PublicAPIIsNullTest()
|
|
||||||
{
|
|
||||||
//Assert.Throws(typeof(Flow.LauncherFatalException), () => PluginManager.Initialize(null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,13 +4,16 @@
|
||||||
|
|
||||||
<!--Dialogues-->
|
<!--Dialogues-->
|
||||||
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">Downloading plugin</system:String>
|
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">Downloading plugin</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_please_wait">Please wait...</system:String>
|
|
||||||
<system:String x:Key="plugin_pluginsmanager_download_success">Successfully downloaded</system:String>
|
<system:String x:Key="plugin_pluginsmanager_download_success">Successfully downloaded</system:String>
|
||||||
|
<system:String x:Key="plugin_pluginsmanager_download_error">Error: Unable to download the plugin</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart.</system:String>
|
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart.</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart.</system:String>
|
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart.</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_install_title">Plugin Install</system:String>
|
<system:String x:Key="plugin_pluginsmanager_install_title">Plugin Install</system:String>
|
||||||
|
<system:String x:Key="plugin_pluginsmanager_install_from_web">Download and install {0}</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_uninstall_title">Plugin Uninstall</system:String>
|
<system:String x:Key="plugin_pluginsmanager_uninstall_title">Plugin Uninstall</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Install failed: unable to find the plugin.json metadata file from the new plugin</system:String>
|
<system:String x:Key="plugin_pluginsmanager_install_success_restart">Plugin successfully installed. Restarting Flow, please wait...</system:String>
|
||||||
|
<system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Unable to find the plugin.json metadata file from the extracted zip file.</system:String>
|
||||||
|
<system:String x:Key="plugin_pluginsmanager_install_error_duplicate">Error: A plugin which has the same or greater version with {0} already exists.</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_install_error_title">Error installing plugin</system:String>
|
<system:String x:Key="plugin_pluginsmanager_install_error_title">Error installing plugin</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_install_error_subtitle">Error occured while trying to install {0}</system:String>
|
<system:String x:Key="plugin_pluginsmanager_install_error_subtitle">Error occured while trying to install {0}</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_update_noresult_title">No update available</system:String>
|
<system:String x:Key="plugin_pluginsmanager_update_noresult_title">No update available</system:String>
|
||||||
|
|
@ -21,12 +24,15 @@
|
||||||
<system:String x:Key="plugin_pluginsmanager_update_alreadyexists">This plugin is already installed</system:String>
|
<system:String x:Key="plugin_pluginsmanager_update_alreadyexists">This plugin is already installed</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_update_failed_title">Plugin Manifest Download Failed</system:String>
|
<system:String x:Key="plugin_pluginsmanager_update_failed_title">Plugin Manifest Download Failed</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_update_failed_subtitle">Please check if you can connect to github.com. This error means you may not be able to install or update plugins.</system:String>
|
<system:String x:Key="plugin_pluginsmanager_update_failed_subtitle">Please check if you can connect to github.com. This error means you may not be able to install or update plugins.</system:String>
|
||||||
|
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning_title">Installing from an unknown source</system:String>
|
||||||
|
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning">You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)</system:String>
|
||||||
|
|
||||||
<!--Controls-->
|
<!--Controls-->
|
||||||
|
|
||||||
<!--Plugin Infos-->
|
<!--Plugin Infos-->
|
||||||
<system:String x:Key="plugin_pluginsmanager_plugin_name">Plugins Manager</system:String>
|
<system:String x:Key="plugin_pluginsmanager_plugin_name">Plugins Manager</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String>
|
<system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String>
|
||||||
|
<system:String x:Key="plugin_pluginsmanager_unknown_author">Unknown Author</system:String>
|
||||||
|
|
||||||
<!--Context menu items-->
|
<!--Context menu items-->
|
||||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_title">Open website</system:String>
|
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_title">Open website</system:String>
|
||||||
|
|
@ -36,6 +42,8 @@
|
||||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_title">Suggest an enhancement or submit an issue</system:String>
|
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_title">Suggest an enhancement or submit an issue</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle">Suggest an enhancement or submit an issue to the plugin developer</system:String>
|
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle">Suggest an enhancement or submit an issue to the plugin developer</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title">Go to Flow's plugins repository</system:String>
|
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title">Go to Flow's plugins repository</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle">Visit the PluginsManifest repository to see comunity-made plugin submissions</system:String>
|
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle">Visit the PluginsManifest repository to see community-made plugin submissions</system:String>
|
||||||
|
|
||||||
|
<!--Settings menu items-->
|
||||||
|
<system:String x:Key="plugin_pluginsmanager_plugin_settings_unknown_source">Install from unknown source warning</system:String>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
<!--Dialogues-->
|
<!--Dialogues-->
|
||||||
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">Sťahovanie pluginu</system:String>
|
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">Sťahovanie pluginu</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_please_wait">Čakajte, prosím…</system:String>
|
|
||||||
<system:String x:Key="plugin_pluginsmanager_download_success">Úspešne stiahnuté</system:String>
|
<system:String x:Key="plugin_pluginsmanager_download_success">Úspešne stiahnuté</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} od {1} {2}{3}Chcete odinštalovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje.</system:String>
|
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} od {1} {2}{3}Chcete odinštalovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje.</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Chcete nainštalovať tento plugin? Po nainštalovaní sa Flow automaticky reštartuje.</system:String>
|
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Chcete nainštalovať tento plugin? Po nainštalovaní sa Flow automaticky reštartuje.</system:String>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
<!--Dialogues-->
|
<!--Dialogues-->
|
||||||
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">下载插件</system:String>
|
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">下载插件</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_please_wait">请稍等...</system:String>
|
|
||||||
<system:String x:Key="plugin_pluginsmanager_download_success">下载完成</system:String>
|
<system:String x:Key="plugin_pluginsmanager_download_success">下载完成</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} by {1} {2}{3} 您要卸载此插件吗? 卸载后,Flow Launcher 将自动重启。</system:String>
|
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} by {1} {2}{3} 您要卸载此插件吗? 卸载后,Flow Launcher 将自动重启。</system:String>
|
||||||
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3} 您要安装此插件吗? 安装后,Flow Launcher 将自动重启</system:String>
|
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3} 您要安装此插件吗? 安装后,Flow Launcher 将自动重启</system:String>
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||||
{
|
{
|
||||||
var search = query.Search.ToLower();
|
var search = query.Search;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(search))
|
if (string.IsNullOrWhiteSpace(search))
|
||||||
return pluginManager.GetDefaultHotKeys();
|
return pluginManager.GetDefaultHotKeys();
|
||||||
|
|
@ -70,9 +70,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
return search switch
|
return search switch
|
||||||
{
|
{
|
||||||
var s when s.StartsWith(Settings.HotKeyInstall) => await pluginManager.RequestInstallOrUpdate(s, token),
|
//search could be url, no need ToLower() when passed in
|
||||||
var s when s.StartsWith(Settings.HotkeyUninstall) => pluginManager.RequestUninstall(s),
|
var s when s.StartsWith(Settings.HotKeyInstall, StringComparison.OrdinalIgnoreCase)
|
||||||
var s when s.StartsWith(Settings.HotkeyUpdate) => await pluginManager.RequestUpdate(s, token),
|
=> await pluginManager.RequestInstallOrUpdate(search, token),
|
||||||
|
var s when s.StartsWith(Settings.HotkeyUninstall, StringComparison.OrdinalIgnoreCase)
|
||||||
|
=> pluginManager.RequestUninstall(search),
|
||||||
|
var s when s.StartsWith(Settings.HotkeyUpdate, StringComparison.OrdinalIgnoreCase)
|
||||||
|
=> await pluginManager.RequestUpdate(search, token),
|
||||||
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
|
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
|
||||||
{
|
{
|
||||||
hotkey.Score = StringMatcher.FuzzySearch(search, hotkey.Title).Score;
|
hotkey.Score = StringMatcher.FuzzySearch(search, hotkey.Title).Score;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
@ -17,6 +19,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
{
|
{
|
||||||
internal class PluginsManager
|
internal class PluginsManager
|
||||||
{
|
{
|
||||||
|
const string zip = "zip";
|
||||||
|
|
||||||
private PluginInitContext Context { get; set; }
|
private PluginInitContext Context { get; set; }
|
||||||
|
|
||||||
private Settings Settings { get; set; }
|
private Settings Settings { get; set; }
|
||||||
|
|
@ -47,7 +51,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
private Task _downloadManifestTask = Task.CompletedTask;
|
private Task _downloadManifestTask = Task.CompletedTask;
|
||||||
|
|
||||||
|
|
||||||
internal Task UpdateManifestAsync()
|
internal Task UpdateManifestAsync()
|
||||||
{
|
{
|
||||||
if (_downloadManifestTask.Status == TaskStatus.Running)
|
if (_downloadManifestTask.Status == TaskStatus.Running)
|
||||||
|
|
@ -138,13 +141,15 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
MessageBoxButton.YesNo) == MessageBoxResult.No)
|
MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}.zip");
|
// at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
|
||||||
|
var downloadFilename = string.IsNullOrEmpty(plugin.Version)
|
||||||
|
? $"{plugin.Name}-{Guid.NewGuid()}.zip"
|
||||||
|
: $"{plugin.Name}-{plugin.Version}.zip";
|
||||||
|
|
||||||
|
var filePath = Path.Combine(DataLocation.PluginsDirectory, downloadFilename);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
|
|
||||||
|
|
||||||
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
|
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
|
||||||
|
|
||||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
||||||
|
|
@ -154,7 +159,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
if (e is HttpRequestException)
|
||||||
|
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_download_error"),
|
||||||
|
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"));
|
||||||
|
|
||||||
|
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
|
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
|
||||||
plugin.Name));
|
plugin.Name));
|
||||||
|
|
||||||
|
|
@ -163,6 +172,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
|
||||||
|
Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"));
|
||||||
|
|
||||||
Context.API.RestartApp();
|
Context.API.RestartApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,7 +195,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
if (autocompletedResults.Any())
|
if (autocompletedResults.Any())
|
||||||
return autocompletedResults;
|
return autocompletedResults;
|
||||||
|
|
||||||
var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty).TrimStart();
|
var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart();
|
||||||
|
|
||||||
var resultsForUpdate =
|
var resultsForUpdate =
|
||||||
from existingPlugin in Context.API.GetAllPlugins()
|
from existingPlugin in Context.API.GetAllPlugins()
|
||||||
|
|
@ -239,10 +251,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
Task.Run(async delegate
|
Task.Run(async delegate
|
||||||
{
|
{
|
||||||
Context.API.ShowMsg(
|
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
|
|
||||||
|
|
||||||
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
|
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
@ -302,6 +310,62 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal List<Result> InstallFromWeb(string url)
|
||||||
|
{
|
||||||
|
var filename = url.Split("/").Last();
|
||||||
|
var name = filename.Split(string.Format(".{0}", zip)).First();
|
||||||
|
|
||||||
|
var plugin = new UserPlugin
|
||||||
|
{
|
||||||
|
ID = "",
|
||||||
|
Name = name,
|
||||||
|
Version = string.Empty,
|
||||||
|
Author = Context.API.GetTranslation("plugin_pluginsmanager_unknown_author"),
|
||||||
|
UrlDownload = url
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = new Result
|
||||||
|
{
|
||||||
|
Title = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_from_web"), filename),
|
||||||
|
SubTitle = plugin.UrlDownload,
|
||||||
|
IcoPath = icoPath,
|
||||||
|
Action = e =>
|
||||||
|
{
|
||||||
|
if (e.SpecialKeyState.CtrlPressed)
|
||||||
|
{
|
||||||
|
SearchWeb.NewTabInBrowser(plugin.UrlDownload);
|
||||||
|
return ShouldHideWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.WarnFromUnknownSource)
|
||||||
|
{
|
||||||
|
if (!InstallSourceKnown(plugin.UrlDownload)
|
||||||
|
&& MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"),
|
||||||
|
Environment.NewLine),
|
||||||
|
Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning_title"),
|
||||||
|
MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.Current.MainWindow.Hide();
|
||||||
|
_ = InstallOrUpdate(plugin);
|
||||||
|
|
||||||
|
return ShouldHideWindow;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new List<Result> { result };
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool InstallSourceKnown(string url)
|
||||||
|
{
|
||||||
|
var author = url.Split('/')[3];
|
||||||
|
var acceptedSource = "https://github.com";
|
||||||
|
var contructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
|
||||||
|
|
||||||
|
return url.StartsWith(acceptedSource) && Context.API.GetAllPlugins().Any(x => x.Metadata.Website.StartsWith(contructedUrlPart));
|
||||||
|
}
|
||||||
|
|
||||||
internal async ValueTask<List<Result>> RequestInstallOrUpdate(string searchName, CancellationToken token)
|
internal async ValueTask<List<Result>> RequestInstallOrUpdate(string searchName, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (!PluginsManifest.UserPlugins.Any())
|
if (!PluginsManifest.UserPlugins.Any())
|
||||||
|
|
@ -311,7 +375,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty).Trim();
|
var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty, StringComparison.OrdinalIgnoreCase).Trim();
|
||||||
|
|
||||||
|
if (Uri.IsWellFormedUriString(searchNameWithoutKeyword, UriKind.Absolute)
|
||||||
|
&& searchNameWithoutKeyword.Split('.').Last() == zip)
|
||||||
|
return InstallFromWeb(searchNameWithoutKeyword);
|
||||||
|
|
||||||
var results =
|
var results =
|
||||||
PluginsManifest
|
PluginsManifest
|
||||||
|
|
@ -369,11 +437,26 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
|
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
|
||||||
{
|
{
|
||||||
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
|
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"),
|
||||||
return;
|
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
|
||||||
|
|
||||||
|
throw new FileNotFoundException (
|
||||||
|
string.Format("Unable to find plugin.json from the extracted zip file, or this path {0} does not exist", pluginFolderPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}");
|
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
|
||||||
|
{
|
||||||
|
MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name),
|
||||||
|
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
|
||||||
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
string.Format("A plugin with the same ID and version already exists, " +
|
||||||
|
"or the version is greater than this downloaded plugin {0}",
|
||||||
|
plugin.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
var directory = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
|
||||||
|
var newPluginPath = Path.Combine(DataLocation.PluginsDirectory, directory);
|
||||||
|
|
||||||
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
|
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
|
||||||
|
|
||||||
|
|
@ -390,7 +473,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
if (autocompletedResults.Any())
|
if (autocompletedResults.Any())
|
||||||
return autocompletedResults;
|
return autocompletedResults;
|
||||||
|
|
||||||
var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty).TrimStart();
|
var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart();
|
||||||
|
|
||||||
var results = Context.API
|
var results = Context.API
|
||||||
.GetAllPlugins()
|
.GetAllPlugins()
|
||||||
|
|
@ -466,5 +549,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
return new List<Result>();
|
return new List<Result>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool SameOrLesserPluginVersionExists(string metadataPath)
|
||||||
|
{
|
||||||
|
var newMetadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(metadataPath));
|
||||||
|
return Context.API.GetAllPlugins()
|
||||||
|
.Any(x => x.Metadata.ID == newMetadata.ID
|
||||||
|
&& newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
internal class Settings
|
internal class Settings
|
||||||
{
|
{
|
||||||
internal string HotKeyInstall { get; set; } = "install";
|
internal string HotKeyInstall { get; set; } = "install";
|
||||||
|
|
||||||
internal string HotkeyUninstall { get; set; } = "uninstall";
|
internal string HotkeyUninstall { get; set; } = "uninstall";
|
||||||
|
|
||||||
internal string HotkeyUpdate { get; set; } = "update";
|
internal string HotkeyUpdate { get; set; } = "update";
|
||||||
|
|
||||||
|
public bool WarnFromUnknownSource { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,11 @@ namespace Flow.Launcher.Plugin.PluginsManager.ViewModels
|
||||||
Context = context;
|
Context = context;
|
||||||
Settings = settings;
|
Settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool WarnFromUnknownSource
|
||||||
|
{
|
||||||
|
get => Settings.WarnFromUnknownSource;
|
||||||
|
set => Settings.WarnFromUnknownSource = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,18 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:Flow.Launcher.Plugin.PluginsManager.ViewModels"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
<Grid>
|
<Grid Margin="70 15 0 15">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="250"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="{DynamicResource plugin_pluginsmanager_plugin_settings_unknown_source}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="14"/>
|
||||||
|
<CheckBox Grid.Column="1" IsChecked="{Binding WarnFromUnknownSource}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Flow.Launcher.Plugin.PluginsManager.Views
|
||||||
|
|
||||||
this.viewModel = viewModel;
|
this.viewModel = viewModel;
|
||||||
|
|
||||||
//RefreshView();
|
this.DataContext = viewModel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"Name": "Plugins Manager",
|
"Name": "Plugins Manager",
|
||||||
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
|
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
|
||||||
"Author": "Jeremy Wu",
|
"Author": "Jeremy Wu",
|
||||||
"Version": "1.9.1",
|
"Version": "1.10.0",
|
||||||
"Language": "csharp",
|
"Language": "csharp",
|
||||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||||
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",
|
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue