mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into binary_formatter
This commit is contained in:
commit
d57f7189d3
19 changed files with 371 additions and 177 deletions
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Droplex" Version="1.7.0" />
|
||||
<PackageReference Include="FSharp.Core" Version="7.0.400" />
|
||||
<PackageReference Include="FSharp.Core" Version="7.0.401" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
|
||||
<PackageReference Include="StreamJsonRpc" Version="2.16.36" />
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
|
||||
public record JsonRPCQueryResponseModel(int Id,
|
||||
[property: JsonPropertyName("result")] List<JsonRPCResult> Result,
|
||||
IReadOnlyDictionary<string, object> SettingsChanges = null,
|
||||
IReadOnlyDictionary<string, object> SettingsChange = null,
|
||||
string DebugMessage = "",
|
||||
JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
results.AddRange(queryResponseModel.Result);
|
||||
|
||||
Settings?.UpdateSettings(queryResponseModel.SettingsChanges);
|
||||
Settings?.UpdateSettings(queryResponseModel.SettingsChange);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
|
@ -126,14 +126,15 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private async Task InitSettingAsync()
|
||||
{
|
||||
if (!File.Exists(SettingConfigurationPath))
|
||||
return;
|
||||
JsonRpcConfigurationModel configuration = null;
|
||||
if (File.Exists(SettingConfigurationPath))
|
||||
{
|
||||
var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
|
||||
configuration =
|
||||
deserializer.Deserialize<JsonRpcConfigurationModel>(
|
||||
await File.ReadAllTextAsync(SettingConfigurationPath));
|
||||
}
|
||||
|
||||
var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
var configuration =
|
||||
deserializer.Deserialize<JsonRpcConfigurationModel>(
|
||||
await File.ReadAllTextAsync(SettingConfigurationPath));
|
||||
|
||||
Settings ??= new JsonRPCPluginSettings
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
|
@ -10,16 +11,16 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
public class JsonRPCPluginSettings
|
||||
{
|
||||
public required JsonRpcConfigurationModel Configuration { get; init; }
|
||||
public required JsonRpcConfigurationModel? Configuration { get; init; }
|
||||
|
||||
public required string SettingPath { get; init; }
|
||||
public Dictionary<string, FrameworkElement> SettingControls { get; } = new();
|
||||
|
||||
public IReadOnlyDictionary<string, object> Inner => Settings;
|
||||
protected Dictionary<string, object> Settings { get; set; }
|
||||
protected ConcurrentDictionary<string, object> Settings { get; set; }
|
||||
public required IPublicAPI API { get; init; }
|
||||
|
||||
private JsonStorage<Dictionary<string, object>> _storage;
|
||||
private JsonStorage<ConcurrentDictionary<string, object>> _storage;
|
||||
|
||||
// maybe move to resource?
|
||||
private static readonly Thickness settingControlMargin = new(0, 9, 18, 9);
|
||||
|
|
@ -33,9 +34,14 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_storage = new JsonStorage<Dictionary<string, object>>(SettingPath);
|
||||
_storage = new JsonStorage<ConcurrentDictionary<string, object>>(SettingPath);
|
||||
Settings = await _storage.LoadAsync();
|
||||
|
||||
if (Settings != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (type, attributes) in Configuration.Body)
|
||||
{
|
||||
if (attributes.Name == null)
|
||||
|
|
@ -58,10 +64,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
foreach (var (key, value) in settings)
|
||||
{
|
||||
if (Settings.ContainsKey(key))
|
||||
{
|
||||
Settings[key] = value;
|
||||
}
|
||||
Settings[key] = value;
|
||||
|
||||
if (SettingControls.TryGetValue(key, out var control))
|
||||
{
|
||||
|
|
@ -82,6 +85,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
}
|
||||
Save();
|
||||
}
|
||||
|
||||
public async Task SaveAsync()
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private void SetupJsonRPC()
|
||||
{
|
||||
var formatter = new SystemTextJsonFormatter();
|
||||
var formatter = new JsonMessageFormatter();
|
||||
var handler = new NewLineDelimitedMessageHandler(ClientPipe,
|
||||
formatter);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ using Flow.Launcher.Infrastructure.Logger;
|
|||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using ISavable = Flow.Launcher.Plugin.ISavable;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
|
@ -27,9 +29,9 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
public static IPublicAPI API { private set; get; }
|
||||
|
||||
// todo happlebao, this should not be public, the indicator function should be embeded
|
||||
public static PluginsSettings Settings;
|
||||
private static PluginsSettings Settings;
|
||||
private static List<PluginMetadata> _metadatas;
|
||||
private static List<string> _modifiedPlugins = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Directories that will hold Flow Launcher plugin directory
|
||||
|
|
@ -331,5 +333,156 @@ namespace Flow.Launcher.Core.Plugin
|
|||
RemoveActionKeyword(id, oldActionKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
private static bool SameOrLesserPluginVersionExists(string metadataPath)
|
||||
{
|
||||
var newMetadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(metadataPath));
|
||||
return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID
|
||||
&& newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
|
||||
}
|
||||
|
||||
#region Public functions
|
||||
|
||||
public static bool PluginModified(string uuid)
|
||||
{
|
||||
return _modifiedPlugins.Contains(uuid);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update a plugin to new version, from a zip file. Will Delete zip after updating.
|
||||
/// </summary>
|
||||
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
|
||||
{
|
||||
InstallPlugin(newVersion, zipFilePath, checkModified:false);
|
||||
UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
|
||||
_modifiedPlugins.Add(existingVersion.ID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Install a plugin. Will Delete zip after updating.
|
||||
/// </summary>
|
||||
public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
|
||||
{
|
||||
InstallPlugin(plugin, zipFilePath, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uninstall a plugin.
|
||||
/// </summary>
|
||||
public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
|
||||
{
|
||||
UninstallPlugin(plugin, removeSettings, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal functions
|
||||
|
||||
internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified)
|
||||
{
|
||||
if (checkModified && PluginModified(plugin.ID))
|
||||
{
|
||||
// Distinguish exception from installing same or less version
|
||||
throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin));
|
||||
}
|
||||
|
||||
// Unzip plugin files to temp folder
|
||||
var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
|
||||
File.Delete(zipFilePath);
|
||||
|
||||
var pluginFolderPath = 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))
|
||||
{
|
||||
throw new FileNotFoundException($"Unable to find plugin.json from the extracted zip file, or this path {pluginFolderPath} does not exist");
|
||||
}
|
||||
|
||||
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
|
||||
{
|
||||
throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}");
|
||||
}
|
||||
|
||||
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
|
||||
|
||||
var defaultPluginIDs = new List<string>
|
||||
{
|
||||
"0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
|
||||
"CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
|
||||
"572be03c74c642baae319fc283e561a8", // Explorer
|
||||
"6A122269676E40EB86EB543B945932B9", // PluginIndicator
|
||||
"9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
|
||||
"b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
|
||||
"791FC278BA414111B8D1886DFE447410", // Program
|
||||
"D409510CD0D2481F853690A07E6DC426", // Shell
|
||||
"CEA08895D2544B019B2E9C5009600DF4", // Sys
|
||||
"0308FD86DE0A4DEE8D62B9B535370992", // URL
|
||||
"565B73353DBF4806919830B9202EE3BF", // WebSearch
|
||||
"5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
|
||||
};
|
||||
|
||||
// Treat default plugin differently, it needs to be removable along with each flow release
|
||||
var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
|
||||
? DataLocation.PluginsDirectory
|
||||
: Constant.PreinstalledDirectory;
|
||||
|
||||
var newPluginPath = Path.Combine(installDirectory, folderName);
|
||||
|
||||
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
|
||||
|
||||
Directory.Delete(tempFolderPluginPath, true);
|
||||
|
||||
if (checkModified)
|
||||
{
|
||||
_modifiedPlugins.Add(plugin.ID);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified)
|
||||
{
|
||||
if (checkModified && PluginModified(plugin.ID))
|
||||
{
|
||||
throw new ArgumentException($"Plugin {plugin.Name} has been modified");
|
||||
}
|
||||
|
||||
if (removeSettings)
|
||||
{
|
||||
Settings.Plugins.Remove(plugin.ID);
|
||||
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"));
|
||||
|
||||
if (checkModified)
|
||||
{
|
||||
_modifiedPlugins.Add(plugin.ID);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -49,12 +49,12 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="nunit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
<PackageReference Include="NHotkey.Wpf" Version="2.1.1" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||
<PackageReference Include="SharpVectors" Version="1.8.2" />
|
||||
<PackageReference Include="VirtualizingWrapPanel" Version="1.5.7" />
|
||||
<PackageReference Include="VirtualizingWrapPanel" Version="1.5.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.10" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -13,20 +13,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
|
|||
if (string.IsNullOrEmpty(query.Search))
|
||||
return new List<Result>();
|
||||
|
||||
string search = query.Search.ToLower();
|
||||
|
||||
var queriedAccessLinks =
|
||||
accessLinks
|
||||
.Where(x => x.Name.Contains(search, StringComparison.OrdinalIgnoreCase) || x.Path.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
return accessLinks
|
||||
.Where(x => Main.Context.API.FuzzySearch(query.Search, x.Name).IsSearchPrecisionScoreMet() || Main.Context.API.FuzzySearch(query.Search, x.Path).IsSearchPrecisionScoreMet())
|
||||
.OrderBy(x => x.Type)
|
||||
.ThenBy(x => x.Name);
|
||||
|
||||
return queriedAccessLinks.Select(l => l.Type switch
|
||||
{
|
||||
ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore),
|
||||
ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
}).ToList();
|
||||
.ThenBy(x => x.Name)
|
||||
.Select(l => l.Type switch
|
||||
{
|
||||
ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore),
|
||||
ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
internal static List<Result> AccessLinkListAll(Query query, IEnumerable<AccessLink> accessLinks)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"Name": "Explorer",
|
||||
"Description": "Find and manage files and folders via Windows Search or Everything",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "3.1.3",
|
||||
"Version": "3.1.4",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Context = context;
|
||||
}
|
||||
|
||||
private readonly GlyphInfo sourcecodeGlyph = new("/Resources/#Segoe Fluent Icons","\uE943");
|
||||
private readonly GlyphInfo issueGlyph = new("/Resources/#Segoe Fluent Icons", "\ued15");
|
||||
private readonly GlyphInfo manifestGlyph = new("/Resources/#Segoe Fluent Icons", "\uea37");
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
if(selectedResult.ContextData is not UserPlugin pluginManifestInfo)
|
||||
|
|
@ -36,6 +40,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title"),
|
||||
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle"),
|
||||
IcoPath = "Images\\sourcecode.png",
|
||||
Glyph = sourcecodeGlyph,
|
||||
Action = _ =>
|
||||
{
|
||||
Context.API.OpenUrl(pluginManifestInfo.UrlSourceCode);
|
||||
|
|
@ -47,6 +52,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_title"),
|
||||
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle"),
|
||||
IcoPath = "Images\\request.png",
|
||||
Glyph = issueGlyph,
|
||||
Action = _ =>
|
||||
{
|
||||
// standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/Flow.Launcher.Plugin.Putty/tree/master
|
||||
|
|
@ -63,6 +69,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 = "Images\\manifestsite.png",
|
||||
Glyph = manifestGlyph,
|
||||
Action = _ =>
|
||||
{
|
||||
Context.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest");
|
||||
|
|
|
|||
|
|
@ -36,8 +36,4 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -8,7 +8,9 @@
|
|||
<system:String x:Key="plugin_pluginsmanager_download_success">Successfully downloaded {0}</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_no_restart">{0} by {1} {2}{2}Would you like to uninstall this plugin?</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_no_restart">{0} by {1} {2}{2}Would you like to install this plugin?</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_install_title">Plugin Install</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_installing_plugin">Installing Plugin</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_install_from_web">Download and install {0}</system:String>
|
||||
|
|
@ -18,19 +20,25 @@
|
|||
<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_subtitle">Error occurred while trying to install {0}</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_uninstall_error_title">Error uninstalling plugin</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_update_noresult_title">No update available</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_update_noresult_subtitle">All plugins are up to date</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_update_prompt">{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_update_prompt_no_restart">{0} by {1} {2}{2}Would you like to update this plugin?</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_update_title">Plugin Update</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_update_exists">This plugin has an update, would you like to see it?</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_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_success_restart">Plugin {0} successfully updated. Restarting Flow, please wait...</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 -->
|
||||
|
||||
|
||||
<system:String x:Key="plugin_pluginsmanager_install_success_no_restart">Plugin {0} successfully installed. Please restart Flow.</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_uninstall_success_no_restart">Plugin {0} successfully uninstalled. Please restart Flow.</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_update_success_no_restart">Plugin {0} successfully updated. Please restart Flow.</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_modified_error">Plugin {0} has already been modified. Please restart Flow before making any further changes.</system:String>
|
||||
|
||||
<!-- Plugin Infos -->
|
||||
<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>
|
||||
|
|
@ -48,4 +56,5 @@
|
|||
|
||||
<!-- Settings menu items -->
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_settings_unknown_source">Install from unknown source warning</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_settings_auto_restart">Automatically restart Flow Launcher after installing/uninstalling/updating plugins</system:String>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -10,7 +10,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
|
@ -117,9 +116,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return;
|
||||
}
|
||||
|
||||
var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
|
||||
plugin.Name, plugin.Author,
|
||||
Environment.NewLine, Environment.NewLine);
|
||||
string message;
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
{
|
||||
message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
|
||||
plugin.Name, plugin.Author,
|
||||
Environment.NewLine, Environment.NewLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt_no_restart"),
|
||||
plugin.Name, plugin.Author,
|
||||
Environment.NewLine);
|
||||
}
|
||||
|
||||
if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
|
|
@ -130,36 +139,48 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
? $"{plugin.Name}-{Guid.NewGuid()}.zip"
|
||||
: $"{plugin.Name}-{plugin.Version}.zip";
|
||||
|
||||
var filePath = Path.Combine(DataLocation.PluginsDirectory, downloadFilename);
|
||||
var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
|
||||
|
||||
try
|
||||
{
|
||||
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), plugin.Name));
|
||||
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
|
||||
|
||||
Install(plugin, filePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
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"),
|
||||
plugin.Name));
|
||||
|
||||
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e, "InstallOrUpdate");
|
||||
|
||||
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
|
||||
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
|
||||
return;
|
||||
}
|
||||
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"), plugin.Name));
|
||||
|
||||
Context.API.RestartApp();
|
||||
catch (Exception e)
|
||||
{
|
||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
|
||||
plugin.Name));
|
||||
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
{
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"),
|
||||
plugin.Name));
|
||||
Context.API.RestartApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_no_restart"),
|
||||
plugin.Name));
|
||||
}
|
||||
}
|
||||
|
||||
internal async ValueTask<List<Result>> RequestUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false)
|
||||
|
|
@ -172,6 +193,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
on existingPlugin.Metadata.ID equals pluginFromManifest.ID
|
||||
where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) <
|
||||
0 // if current version precedes manifest version
|
||||
&& !PluginManager.PluginModified(existingPlugin.Metadata.ID)
|
||||
select
|
||||
new
|
||||
{
|
||||
|
|
@ -204,36 +226,57 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
IcoPath = x.IcoPath,
|
||||
Action = e =>
|
||||
{
|
||||
string message = string.Format(
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
|
||||
x.Name, x.Author,
|
||||
Environment.NewLine, Environment.NewLine);
|
||||
|
||||
string message;
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
{
|
||||
message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
|
||||
x.Name, x.Author,
|
||||
Environment.NewLine, Environment.NewLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"),
|
||||
x.Name, x.Author,
|
||||
Environment.NewLine);
|
||||
}
|
||||
|
||||
if (MessageBox.Show(message,
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
Uninstall(x.PluginExistingMetadata, false);
|
||||
|
||||
var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
|
||||
var downloadToFilePath = Path.Combine(Path.GetTempPath(),
|
||||
$"{x.Name}-{x.NewVersion}.zip");
|
||||
|
||||
Task.Run(async delegate
|
||||
_ = Task.Run(async delegate
|
||||
{
|
||||
if (File.Exists(downloadToFilePath))
|
||||
{
|
||||
File.Delete(downloadToFilePath);
|
||||
}
|
||||
|
||||
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Context.API.ShowMsg(
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), x.Name));
|
||||
PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath);
|
||||
|
||||
Install(x.PluginNewUserPlugin, downloadToFilePath);
|
||||
|
||||
Context.API.RestartApp();
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
{
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"),
|
||||
x.Name));
|
||||
Context.API.RestartApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"),
|
||||
x.Name));
|
||||
}
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
Log.Exception("PluginsManager", $"Update failed for {x.Name}",
|
||||
t.Exception.InnerException, "RequestUpdate");
|
||||
t.Exception.InnerException);
|
||||
Context.API.ShowMsg(
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||
string.Format(
|
||||
|
|
@ -348,12 +391,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
var results =
|
||||
PluginsManifest
|
||||
.UserPlugins
|
||||
.Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID))
|
||||
.Select(x =>
|
||||
new Result
|
||||
{
|
||||
Title = $"{x.Name} by {x.Author}",
|
||||
SubTitle = x.Description,
|
||||
IcoPath = icoPath,
|
||||
IcoPath = x.IcoPath,
|
||||
Action = e =>
|
||||
{
|
||||
if (e.SpecialKeyState.CtrlPressed)
|
||||
|
|
@ -375,78 +419,29 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
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.Copy(downloadedFilePath, zipFilePath);
|
||||
|
||||
File.Delete(downloadedFilePath);
|
||||
|
||||
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))
|
||||
throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath);
|
||||
try
|
||||
{
|
||||
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"),
|
||||
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));
|
||||
PluginManager.InstallPlugin(plugin, downloadedFilePath);
|
||||
File.Delete(downloadedFilePath);
|
||||
}
|
||||
|
||||
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
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));
|
||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
|
||||
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name));
|
||||
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
|
||||
}
|
||||
catch (ArgumentException e) {
|
||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name));
|
||||
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
|
||||
}
|
||||
|
||||
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
|
||||
|
||||
var defaultPluginIDs = new List<string>
|
||||
{
|
||||
"0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
|
||||
"CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
|
||||
"572be03c74c642baae319fc283e561a8", // Explorer
|
||||
"6A122269676E40EB86EB543B945932B9", // PluginIndicator
|
||||
"9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
|
||||
"b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
|
||||
"791FC278BA414111B8D1886DFE447410", // Program
|
||||
"D409510CD0D2481F853690A07E6DC426", // Shell
|
||||
"CEA08895D2544B019B2E9C5009600DF4", // Sys
|
||||
"0308FD86DE0A4DEE8D62B9B535370992", // URL
|
||||
"565B73353DBF4806919830B9202EE3BF", // WebSearch
|
||||
"5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
|
||||
};
|
||||
|
||||
// Treat default plugin differently, it needs to be removable along with each flow release
|
||||
var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
|
||||
? DataLocation.PluginsDirectory
|
||||
: Constant.PreinstalledDirectory;
|
||||
|
||||
var newPluginPath = Path.Combine(installDirectory, folderName);
|
||||
|
||||
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
|
||||
|
||||
Directory.Delete(pluginFolderPath, true);
|
||||
}
|
||||
|
||||
internal List<Result> RequestUninstall(string search)
|
||||
|
|
@ -461,10 +456,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
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);
|
||||
string message;
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
{
|
||||
message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
|
||||
x.Metadata.Name, x.Metadata.Author,
|
||||
Environment.NewLine, Environment.NewLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt_no_restart"),
|
||||
x.Metadata.Name, x.Metadata.Author,
|
||||
Environment.NewLine);
|
||||
}
|
||||
|
||||
if (MessageBox.Show(message,
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
|
||||
|
|
@ -472,7 +476,16 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
{
|
||||
Application.Current.MainWindow.Hide();
|
||||
Uninstall(x.Metadata);
|
||||
Context.API.RestartApp();
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
{
|
||||
Context.API.RestartApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_success_no_restart"),
|
||||
x.Metadata.Name));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -484,24 +497,18 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return Search(results, search);
|
||||
}
|
||||
|
||||
private void Uninstall(PluginMetadata plugin, bool removedSetting = true)
|
||||
private void Uninstall(PluginMetadata plugin)
|
||||
{
|
||||
if (removedSetting)
|
||||
try
|
||||
{
|
||||
PluginManager.Settings.Plugins.Remove(plugin.ID);
|
||||
PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
|
||||
PluginManager.UninstallPlugin(plugin, removeSettings:true);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
|
||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
|
||||
}
|
||||
|
||||
// Marked for deletion. Will be deleted on next start up
|
||||
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,5 +9,7 @@
|
|||
internal const string UpdateCommand = "update";
|
||||
|
||||
public bool WarnFromUnknownSource { get; set; } = true;
|
||||
|
||||
public bool AutoRestartAfterChanging { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,5 +17,11 @@
|
|||
get => Settings.WarnFromUnknownSource;
|
||||
set => Settings.WarnFromUnknownSource = value;
|
||||
}
|
||||
|
||||
public bool AutoRestartAfterChanging
|
||||
{
|
||||
get => Settings.AutoRestartAfterChanging;
|
||||
set => Settings.AutoRestartAfterChanging = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,22 @@
|
|||
<ColumnDefinition Width="400" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Padding="8,0,0,0"
|
||||
Content="{DynamicResource plugin_pluginsmanager_plugin_settings_unknown_source}"
|
||||
IsChecked="{Binding WarnFromUnknownSource}" />
|
||||
<CheckBox
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Margin="0,20,0,0"
|
||||
Padding="8,0,0,0"
|
||||
Content="{DynamicResource plugin_pluginsmanager_plugin_settings_auto_restart}"
|
||||
IsChecked="{Binding AutoRestartAfterChanging}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
|
|||
Loading…
Reference in a new issue