Skip to content

Commit ba45069

Browse files
authored
Merge branch 'dev' into move-exception-message-to-result
2 parents f684883 + 9459a37 commit ba45069

File tree

30 files changed

+1275
-1044
lines changed

30 files changed

+1275
-1044
lines changed

.github/actions/spelling/expect.txt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
crowdin
22
DWM
33
workflows
4-
Wpf
54
wpf
65
actionkeyword
76
stackoverflow
@@ -20,9 +19,7 @@ Prioritise
2019
Segoe
2120
Google
2221
Customise
23-
UWP
2422
uwp
25-
Uwp
2623
Bokmal
2724
Bokm
2825
uninstallation
@@ -61,7 +58,6 @@ popup
6158
ptr
6259
pluginindicator
6360
TobiasSekan
64-
Img
6561
img
6662
resx
6763
bak
@@ -78,7 +74,6 @@ WCA_ACCENT_POLICY
7874
HGlobal
7975
dopusrt
8076
firefox
81-
Firefox
8277
msedge
8378
svgc
8479
ime
@@ -87,7 +82,6 @@ txb
8782
btn
8883
otf
8984
searchplugin
90-
Noresult
9185
wpftk
9286
mkv
9387
flac
@@ -108,4 +102,6 @@ Preinstalled
108102
errormetadatafile
109103
noresult
110104
pluginsmanager
111-
alreadyexists
105+
alreadyexists
106+
JsonRPC
107+
JsonRPCV2

.github/actions/spelling/patterns.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,6 @@
118118

119119
# UWP
120120
[Uu][Ww][Pp]
121+
122+
# version suffix <word>v#
123+
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))

.github/workflows/spelling.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
steps:
7474
- name: check-spelling
7575
id: spelling
76-
uses: check-spelling/check-spelling@v0.0.22
76+
uses: check-spelling/check-spelling@prerelease
7777
with:
7878
suppress_push_for_open_pull_request: 1
7979
checkout: true
@@ -91,10 +91,9 @@ jobs:
9191
extra_dictionaries:
9292
cspell:software-terms/dict/softwareTerms.txt
9393
cspell:win32/src/win32.txt
94-
cspell:php/src/php.txt
9594
cspell:filetypes/filetypes.txt
9695
cspell:csharp/csharp.txt
97-
cspell:dotnet/src/dotnet.txt
96+
cspell:dotnet/dict/dotnet.txt
9897
cspell:python/src/common/extra.txt
9998
cspell:python/src/python/python-lib.txt
10099
cspell:aws/aws.txt
@@ -130,7 +129,7 @@ jobs:
130129
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
131130
steps:
132131
- name: comment
133-
uses: check-spelling/check-spelling@v0.0.22
132+
uses: check-spelling/check-spelling@prerelease
134133
with:
135134
checkout: true
136135
spell_check_this: check-spelling/spell-check-this@main

Flow.Launcher.Core/Flow.Launcher.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
<ItemGroup>
5656
<PackageReference Include="Droplex" Version="1.7.0" />
57-
<PackageReference Include="FSharp.Core" Version="7.0.400" />
57+
<PackageReference Include="FSharp.Core" Version="7.0.401" />
5858
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
5959
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
6060
<PackageReference Include="StreamJsonRpc" Version="2.16.36" />

Flow.Launcher.Core/Plugin/JsonPRCModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public record JsonRPCErrorModel(int Code, string Message, string Data);
2525
public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
2626
public record JsonRPCQueryResponseModel(int Id,
2727
[property: JsonPropertyName("result")] List<JsonRPCResult> Result,
28-
IReadOnlyDictionary<string, object> SettingsChanges = null,
28+
IReadOnlyDictionary<string, object> SettingsChange = null,
2929
string DebugMessage = "",
3030
JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
3131

Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ protected List<Result> ParseResults(JsonRPCQueryResponseModel queryResponseModel
9494

9595
results.AddRange(queryResponseModel.Result);
9696

97-
Settings?.UpdateSettings(queryResponseModel.SettingsChanges);
97+
Settings?.UpdateSettings(queryResponseModel.SettingsChange);
9898

9999
return results;
100100
}
@@ -126,14 +126,15 @@ protected void ExecuteFlowLauncherAPI(string method, object[] parameters)
126126

127127
private async Task InitSettingAsync()
128128
{
129-
if (!File.Exists(SettingConfigurationPath))
130-
return;
129+
JsonRpcConfigurationModel configuration = null;
130+
if (File.Exists(SettingConfigurationPath))
131+
{
132+
var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
133+
configuration =
134+
deserializer.Deserialize<JsonRpcConfigurationModel>(
135+
await File.ReadAllTextAsync(SettingConfigurationPath));
136+
}
131137

132-
var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
133-
.Build();
134-
var configuration =
135-
deserializer.Deserialize<JsonRpcConfigurationModel>(
136-
await File.ReadAllTextAsync(SettingConfigurationPath));
137138

138139
Settings ??= new JsonRPCPluginSettings
139140
{

Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Concurrent;
2+
using System.Collections.Generic;
23
using System.Threading.Tasks;
34
using System.Windows;
45
using System.Windows.Controls;
@@ -10,16 +11,16 @@ namespace Flow.Launcher.Core.Plugin
1011
{
1112
public class JsonRPCPluginSettings
1213
{
13-
public required JsonRpcConfigurationModel Configuration { get; init; }
14+
public required JsonRpcConfigurationModel? Configuration { get; init; }
1415

1516
public required string SettingPath { get; init; }
1617
public Dictionary<string, FrameworkElement> SettingControls { get; } = new();
1718

1819
public IReadOnlyDictionary<string, object> Inner => Settings;
19-
protected Dictionary<string, object> Settings { get; set; }
20+
protected ConcurrentDictionary<string, object> Settings { get; set; }
2021
public required IPublicAPI API { get; init; }
2122

22-
private JsonStorage<Dictionary<string, object>> _storage;
23+
private JsonStorage<ConcurrentDictionary<string, object>> _storage;
2324

2425
// maybe move to resource?
2526
private static readonly Thickness settingControlMargin = new(0, 9, 18, 9);
@@ -33,9 +34,14 @@ public class JsonRPCPluginSettings
3334

3435
public async Task InitializeAsync()
3536
{
36-
_storage = new JsonStorage<Dictionary<string, object>>(SettingPath);
37+
_storage = new JsonStorage<ConcurrentDictionary<string, object>>(SettingPath);
3738
Settings = await _storage.LoadAsync();
3839

40+
if (Settings != null)
41+
{
42+
return;
43+
}
44+
3945
foreach (var (type, attributes) in Configuration.Body)
4046
{
4147
if (attributes.Name == null)
@@ -58,10 +64,7 @@ public void UpdateSettings(IReadOnlyDictionary<string, object> settings)
5864

5965
foreach (var (key, value) in settings)
6066
{
61-
if (Settings.ContainsKey(key))
62-
{
63-
Settings[key] = value;
64-
}
67+
Settings[key] = value;
6568

6669
if (SettingControls.TryGetValue(key, out var control))
6770
{
@@ -82,6 +85,7 @@ public void UpdateSettings(IReadOnlyDictionary<string, object> settings)
8285
}
8386
}
8487
}
88+
Save();
8589
}
8690

8791
public async Task SaveAsync()

Flow.Launcher.Core/Plugin/PluginManager.cs

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using Flow.Launcher.Infrastructure.UserSettings;
1212
using Flow.Launcher.Plugin;
1313
using ISavable = Flow.Launcher.Plugin.ISavable;
14+
using Flow.Launcher.Plugin.SharedCommands;
15+
using System.Text.Json;
1416

1517
namespace Flow.Launcher.Core.Plugin
1618
{
@@ -27,9 +29,9 @@ public static class PluginManager
2729

2830
public static IPublicAPI API { private set; get; }
2931

30-
// todo happlebao, this should not be public, the indicator function should be embeded
31-
public static PluginsSettings Settings;
32+
private static PluginsSettings Settings;
3233
private static List<PluginMetadata> _metadatas;
34+
private static List<string> _modifiedPlugins = new List<string>();
3335

3436
/// <summary>
3537
/// Directories that will hold Flow Launcher plugin directory
@@ -343,5 +345,156 @@ public static void ReplaceActionKeyword(string id, string oldActionKeyword, stri
343345
RemoveActionKeyword(id, oldActionKeyword);
344346
}
345347
}
348+
349+
private static string GetContainingFolderPathAfterUnzip(string unzippedParentFolderPath)
350+
{
351+
var unzippedFolderCount = Directory.GetDirectories(unzippedParentFolderPath).Length;
352+
var unzippedFilesCount = Directory.GetFiles(unzippedParentFolderPath).Length;
353+
354+
// adjust path depending on how the plugin is zipped up
355+
// the recommended should be to zip up the folder not the contents
356+
if (unzippedFolderCount == 1 && unzippedFilesCount == 0)
357+
// folder is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/pluginFolderName/
358+
return Directory.GetDirectories(unzippedParentFolderPath)[0];
359+
360+
if (unzippedFilesCount > 1)
361+
// content is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/
362+
return unzippedParentFolderPath;
363+
364+
return string.Empty;
365+
}
366+
367+
private static bool SameOrLesserPluginVersionExists(string metadataPath)
368+
{
369+
var newMetadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(metadataPath));
370+
return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID
371+
&& newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
372+
}
373+
374+
#region Public functions
375+
376+
public static bool PluginModified(string uuid)
377+
{
378+
return _modifiedPlugins.Contains(uuid);
379+
}
380+
381+
382+
/// <summary>
383+
/// Update a plugin to new version, from a zip file. Will Delete zip after updating.
384+
/// </summary>
385+
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
386+
{
387+
InstallPlugin(newVersion, zipFilePath, checkModified:false);
388+
UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
389+
_modifiedPlugins.Add(existingVersion.ID);
390+
}
391+
392+
/// <summary>
393+
/// Install a plugin. Will Delete zip after updating.
394+
/// </summary>
395+
public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
396+
{
397+
InstallPlugin(plugin, zipFilePath, true);
398+
}
399+
400+
/// <summary>
401+
/// Uninstall a plugin.
402+
/// </summary>
403+
public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
404+
{
405+
UninstallPlugin(plugin, removeSettings, true);
406+
}
407+
408+
#endregion
409+
410+
#region Internal functions
411+
412+
internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified)
413+
{
414+
if (checkModified && PluginModified(plugin.ID))
415+
{
416+
// Distinguish exception from installing same or less version
417+
throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin));
418+
}
419+
420+
// Unzip plugin files to temp folder
421+
var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
422+
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
423+
File.Delete(zipFilePath);
424+
425+
var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
426+
427+
var metadataJsonFilePath = string.Empty;
428+
if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName)))
429+
metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName);
430+
431+
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
432+
{
433+
throw new FileNotFoundException($"Unable to find plugin.json from the extracted zip file, or this path {pluginFolderPath} does not exist");
434+
}
435+
436+
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
437+
{
438+
throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}");
439+
}
440+
441+
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
442+
443+
var defaultPluginIDs = new List<string>
444+
{
445+
"0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
446+
"CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
447+
"572be03c74c642baae319fc283e561a8", // Explorer
448+
"6A122269676E40EB86EB543B945932B9", // PluginIndicator
449+
"9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
450+
"b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
451+
"791FC278BA414111B8D1886DFE447410", // Program
452+
"D409510CD0D2481F853690A07E6DC426", // Shell
453+
"CEA08895D2544B019B2E9C5009600DF4", // Sys
454+
"0308FD86DE0A4DEE8D62B9B535370992", // URL
455+
"565B73353DBF4806919830B9202EE3BF", // WebSearch
456+
"5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
457+
};
458+
459+
// Treat default plugin differently, it needs to be removable along with each flow release
460+
var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
461+
? DataLocation.PluginsDirectory
462+
: Constant.PreinstalledDirectory;
463+
464+
var newPluginPath = Path.Combine(installDirectory, folderName);
465+
466+
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
467+
468+
Directory.Delete(tempFolderPluginPath, true);
469+
470+
if (checkModified)
471+
{
472+
_modifiedPlugins.Add(plugin.ID);
473+
}
474+
}
475+
476+
internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified)
477+
{
478+
if (checkModified && PluginModified(plugin.ID))
479+
{
480+
throw new ArgumentException($"Plugin {plugin.Name} has been modified");
481+
}
482+
483+
if (removeSettings)
484+
{
485+
Settings.Plugins.Remove(plugin.ID);
486+
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
487+
}
488+
489+
// Marked for deletion. Will be deleted on next start up
490+
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
491+
492+
if (checkModified)
493+
{
494+
_modifiedPlugins.Add(plugin.ID);
495+
}
496+
}
497+
498+
#endregion
346499
}
347500
}

Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal abstract class ProcessStreamPluginV2 : JsonRPCPluginV2
1717

1818
protected abstract ProcessStartInfo StartInfo { get; set; }
1919

20-
public Process ClientProcess { get; set; }
20+
protected Process ClientProcess { get; set; }
2121

2222
public override async Task InitAsync(PluginInitContext context)
2323
{
@@ -33,6 +33,8 @@ public override async Task InitAsync(PluginInitContext context)
3333

3434
SetupPipe(ClientProcess);
3535

36+
ErrorStream = ClientProcess.StandardError;
37+
3638
await base.InitAsync(context);
3739
}
3840

0 commit comments

Comments
 (0)