Skip to content

Commit 47a91e7

Browse files
authored
Merge pull request #1694 from Flow-Launcher/dev
Release 1.11.0 | Plugin 3.0.1
2 parents ebbf3fe + e0cd63a commit 47a91e7

File tree

139 files changed

+2227
-796
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+2227
-796
lines changed

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ updates:
1515
- "jjw24"
1616
- "taooceros"
1717
- "JohnTheGr8"
18+
- package-ecosystem: "github-actions"
19+
directory: "/"
20+
schedule:
21+
interval: "daily"

.github/workflows/winget.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: Publish to Winget
2+
3+
on:
4+
release:
5+
types: [released]
6+
7+
jobs:
8+
publish:
9+
# Action can only be run on windows
10+
runs-on: windows-latest
11+
steps:
12+
- uses: vedantmgoyal2009/winget-releaser@v1
13+
with:
14+
identifier: Flow-Launcher.Flow-Launcher
15+
token: ${{ secrets.WINGET_TOKEN }}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
using Flow.Launcher.Infrastructure.Logger;
2+
using Flow.Launcher.Infrastructure.UserSettings;
3+
using Flow.Launcher.Plugin;
4+
using Flow.Launcher.Plugin.SharedCommands;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Windows.Forms;
10+
11+
namespace Flow.Launcher.Core.ExternalPlugins.Environments
12+
{
13+
public abstract class AbstractPluginEnvironment
14+
{
15+
internal abstract string Language { get; }
16+
17+
internal abstract string EnvName { get; }
18+
19+
internal abstract string EnvPath { get; }
20+
21+
internal abstract string InstallPath { get; }
22+
23+
internal abstract string ExecutablePath { get; }
24+
25+
internal virtual string FileDialogFilter => string.Empty;
26+
27+
internal abstract string PluginsSettingsFilePath { get; set; }
28+
29+
internal List<PluginMetadata> PluginMetadataList;
30+
31+
internal PluginsSettings PluginSettings;
32+
33+
internal AbstractPluginEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings)
34+
{
35+
PluginMetadataList = pluginMetadataList;
36+
PluginSettings = pluginSettings;
37+
}
38+
39+
internal IEnumerable<PluginPair> Setup()
40+
{
41+
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
42+
return new List<PluginPair>();
43+
44+
// TODO: Remove. This is backwards compatibility for 1.10.0 release- changed PythonEmbeded to Environments/Python
45+
if (Language.Equals(AllowedLanguage.Python, StringComparison.OrdinalIgnoreCase))
46+
{
47+
FilesFolders.RemoveFolderIfExists(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable"));
48+
49+
if (!string.IsNullOrEmpty(PluginSettings.PythonDirectory) && PluginSettings.PythonDirectory.StartsWith(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable")))
50+
{
51+
InstallEnvironment();
52+
PluginSettings.PythonDirectory = string.Empty;
53+
}
54+
}
55+
56+
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
57+
{
58+
// Ensure latest only if user is using Flow's environment setup.
59+
if (PluginsSettingsFilePath.StartsWith(EnvPath, StringComparison.OrdinalIgnoreCase))
60+
EnsureLatestInstalled(ExecutablePath, PluginsSettingsFilePath, EnvPath);
61+
62+
return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
63+
}
64+
65+
if (MessageBox.Show($"Flow detected you have installed {Language} plugins, which " +
66+
$"will require {EnvName} to run. Would you like to download {EnvName}? " +
67+
Environment.NewLine + Environment.NewLine +
68+
"Click no if it's already installed, " +
69+
$"and you will be prompted to select the folder that contains the {EnvName} executable",
70+
string.Empty, MessageBoxButtons.YesNo) == DialogResult.No)
71+
{
72+
var msg = $"Please select the {EnvName} executable";
73+
var selectedFile = string.Empty;
74+
75+
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
76+
77+
if (!string.IsNullOrEmpty(selectedFile))
78+
PluginsSettingsFilePath = selectedFile;
79+
80+
// Nothing selected because user pressed cancel from the file dialog window
81+
if (string.IsNullOrEmpty(selectedFile))
82+
InstallEnvironment();
83+
}
84+
else
85+
{
86+
InstallEnvironment();
87+
}
88+
89+
if (FilesFolders.FileExists(PluginsSettingsFilePath))
90+
{
91+
return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
92+
}
93+
else
94+
{
95+
MessageBox.Show(
96+
$"Unable to set {Language} executable path, please try from Flow's settings (scroll down to the bottom).");
97+
Log.Error("PluginsLoader",
98+
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
99+
$"{Language}Environment");
100+
101+
return new List<PluginPair>();
102+
}
103+
}
104+
105+
internal abstract void InstallEnvironment();
106+
107+
private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath)
108+
{
109+
if (expectedPath == currentPath)
110+
return;
111+
112+
FilesFolders.RemoveFolderIfExists(installedDirPath);
113+
114+
InstallEnvironment();
115+
116+
}
117+
118+
internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata);
119+
120+
private IEnumerable<PluginPair> SetPathForPluginPairs(string filePath, string languageToSet)
121+
{
122+
var pluginPairs = new List<PluginPair>();
123+
124+
foreach (var metadata in PluginMetadataList)
125+
{
126+
if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase))
127+
pluginPairs.Add(CreatePluginPair(filePath, metadata));
128+
}
129+
130+
return pluginPairs;
131+
}
132+
133+
private string GetFileFromDialog(string title, string filter = "")
134+
{
135+
var dlg = new OpenFileDialog
136+
{
137+
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
138+
Multiselect = false,
139+
CheckFileExists = true,
140+
CheckPathExists = true,
141+
Title = title,
142+
Filter = filter
143+
};
144+
145+
var result = dlg.ShowDialog();
146+
if (result == DialogResult.OK)
147+
{
148+
return dlg.FileName;
149+
}
150+
else
151+
{
152+
return string.Empty;
153+
}
154+
}
155+
156+
/// <summary>
157+
/// After app updated while in portable mode or switched between portable/roaming mode,
158+
/// need to update each plugin's executable path so user will not be prompted again to reinstall the environments.
159+
/// </summary>
160+
/// <param name="settings"></param>
161+
public static void PreStartPluginExecutablePathUpdate(Settings settings)
162+
{
163+
if (DataLocation.PortableDataLocationInUse())
164+
{
165+
// When user is using portable but has moved flow to a different location
166+
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName)
167+
&& !settings.PluginSettings.PythonExecutablePath.StartsWith(DataLocation.PortableDataPath))
168+
{
169+
settings.PluginSettings.PythonExecutablePath
170+
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
171+
}
172+
173+
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName)
174+
&& !settings.PluginSettings.NodeExecutablePath.StartsWith(DataLocation.PortableDataPath))
175+
{
176+
settings.PluginSettings.NodeExecutablePath
177+
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
178+
}
179+
180+
// When user has switched from roaming to portable
181+
if (IsUsingRoamingPath(settings.PluginSettings.PythonExecutablePath))
182+
{
183+
settings.PluginSettings.PythonExecutablePath
184+
= settings.PluginSettings.PythonExecutablePath.Replace(DataLocation.RoamingDataPath, DataLocation.PortableDataPath);
185+
}
186+
187+
if (IsUsingRoamingPath(settings.PluginSettings.NodeExecutablePath))
188+
{
189+
settings.PluginSettings.NodeExecutablePath
190+
= settings.PluginSettings.NodeExecutablePath.Replace(DataLocation.RoamingDataPath, DataLocation.PortableDataPath);
191+
}
192+
}
193+
else
194+
{
195+
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName))
196+
settings.PluginSettings.PythonExecutablePath
197+
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
198+
199+
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName))
200+
settings.PluginSettings.NodeExecutablePath
201+
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
202+
}
203+
}
204+
205+
private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName)
206+
{
207+
if (string.IsNullOrEmpty(filePath))
208+
return false;
209+
210+
// DataLocation.PortableDataPath returns the current portable path, this determines if an out
211+
// of date path is also a portable path.
212+
var portableAppEnvLocation = $"UserData\\{DataLocation.PluginEnvironments}\\{pluginEnvironmentName}";
213+
214+
return filePath.Contains(portableAppEnvLocation);
215+
}
216+
217+
private static bool IsUsingRoamingPath(string filePath)
218+
{
219+
if (string.IsNullOrEmpty(filePath))
220+
return false;
221+
222+
return filePath.StartsWith(DataLocation.RoamingDataPath);
223+
}
224+
225+
private static string GetUpdatedEnvironmentPath(string filePath)
226+
{
227+
var index = filePath.IndexOf(DataLocation.PluginEnvironments);
228+
229+
// get the substring after "Environments" because we can not determine it dynamically
230+
var ExecutablePathSubstring = filePath.Substring(index + DataLocation.PluginEnvironments.Count());
231+
return $"{DataLocation.PluginEnvironmentsPath}{ExecutablePathSubstring}";
232+
}
233+
}
234+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Collections.Generic;
2+
using Flow.Launcher.Infrastructure.UserSettings;
3+
using Flow.Launcher.Plugin;
4+
5+
namespace Flow.Launcher.Core.ExternalPlugins.Environments
6+
{
7+
8+
internal class JavaScriptEnvironment : TypeScriptEnvironment
9+
{
10+
internal override string Language => AllowedLanguage.JavaScript;
11+
12+
internal JavaScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
13+
}
14+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Droplex;
2+
using Flow.Launcher.Core.Plugin;
3+
using Flow.Launcher.Infrastructure.UserSettings;
4+
using Flow.Launcher.Plugin;
5+
using Flow.Launcher.Plugin.SharedCommands;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
9+
namespace Flow.Launcher.Core.ExternalPlugins.Environments
10+
{
11+
internal class PythonEnvironment : AbstractPluginEnvironment
12+
{
13+
internal override string Language => AllowedLanguage.Python;
14+
15+
internal override string EnvName => DataLocation.PythonEnvironmentName;
16+
17+
internal override string EnvPath => Path.Combine(DataLocation.PluginEnvironmentsPath, EnvName);
18+
19+
internal override string InstallPath => Path.Combine(EnvPath, "PythonEmbeddable-v3.8.9");
20+
21+
internal override string ExecutablePath => Path.Combine(InstallPath, "pythonw.exe");
22+
23+
internal override string FileDialogFilter => "Python|pythonw.exe";
24+
25+
internal override string PluginsSettingsFilePath { get => PluginSettings.PythonExecutablePath; set => PluginSettings.PythonExecutablePath = value; }
26+
27+
internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
28+
29+
internal override void InstallEnvironment()
30+
{
31+
FilesFolders.RemoveFolderIfExists(InstallPath);
32+
33+
// Python 3.8.9 is used for Windows 7 compatibility
34+
DroplexPackage.Drop(App.python_3_8_9_embeddable, InstallPath).Wait();
35+
36+
PluginsSettingsFilePath = ExecutablePath;
37+
}
38+
39+
internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)
40+
{
41+
return new PluginPair
42+
{
43+
Plugin = new PythonPlugin(filePath),
44+
Metadata = metadata
45+
};
46+
}
47+
}
48+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Collections.Generic;
2+
using Droplex;
3+
using Flow.Launcher.Infrastructure.UserSettings;
4+
using Flow.Launcher.Plugin.SharedCommands;
5+
using Flow.Launcher.Plugin;
6+
using System.IO;
7+
using Flow.Launcher.Core.Plugin;
8+
9+
namespace Flow.Launcher.Core.ExternalPlugins.Environments
10+
{
11+
internal class TypeScriptEnvironment : AbstractPluginEnvironment
12+
{
13+
internal override string Language => AllowedLanguage.TypeScript;
14+
15+
internal override string EnvName => DataLocation.NodeEnvironmentName;
16+
17+
internal override string EnvPath => Path.Combine(DataLocation.PluginEnvironmentsPath, EnvName);
18+
19+
internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
20+
internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
21+
22+
internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; }
23+
24+
internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
25+
26+
internal override void InstallEnvironment()
27+
{
28+
FilesFolders.RemoveFolderIfExists(InstallPath);
29+
30+
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
31+
32+
PluginsSettingsFilePath = ExecutablePath;
33+
}
34+
35+
internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)
36+
{
37+
return new PluginPair
38+
{
39+
Plugin = new NodePlugin(filePath),
40+
Metadata = metadata
41+
};
42+
}
43+
}
44+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
</ItemGroup>
5454

5555
<ItemGroup>
56-
<PackageReference Include="Droplex" Version="1.4.1" />
56+
<PackageReference Include="Droplex" Version="1.6.0" />
5757
<PackageReference Include="FSharp.Core" Version="6.0.6" />
5858
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.1" />
5959
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />

Flow.Launcher.Core/Plugin/ExecutablePlugin.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ namespace Flow.Launcher.Core.Plugin
99
internal class ExecutablePlugin : JsonRPCPlugin
1010
{
1111
private readonly ProcessStartInfo _startInfo;
12-
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
1312

1413
public ExecutablePlugin(string filename)
1514
{

Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProv
3737
{
3838
protected PluginInitContext context;
3939
public const string JsonRPC = "JsonRPC";
40-
/// <summary>
41-
/// The language this JsonRPCPlugin support
42-
/// </summary>
43-
public abstract string SupportedLanguage { get; set; }
40+
4441
protected abstract Task<Stream> RequestAsync(JsonRPCRequestModel rpcRequest, CancellationToken token = default);
4542
protected abstract string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default);
4643

0 commit comments

Comments
 (0)