Skip to content
19 changes: 10 additions & 9 deletions Flow.Launcher.Core/Configuration/Portable.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
using Microsoft.Win32;
using Squirrel;
using System;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using System.Linq;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.Win32;
using Squirrel;

namespace Flow.Launcher.Core.Configuration
{
public class Portable : IPortable
{
private static readonly string ClassName = nameof(Portable);

private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();

/// <summary>
Expand Down Expand Up @@ -51,7 +52,7 @@ public void DisablePortableMode()
}
catch (Exception e)
{
Log.Exception("|Portable.DisablePortableMode|Error occurred while disabling portable mode", e);
API.LogException(ClassName, "Error occurred while disabling portable mode", e);
}
}

Expand All @@ -75,7 +76,7 @@ public void EnablePortableMode()
}
catch (Exception e)
{
Log.Exception("|Portable.EnablePortableMode|Error occurred while enabling portable mode", e);
API.LogException(ClassName, "Error occurred while enabling portable mode", e);
}
}

Expand Down
69 changes: 45 additions & 24 deletions Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
using System;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Sockets;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Plugin;

namespace Flow.Launcher.Core.ExternalPlugins
{
public record CommunityPluginSource(string ManifestFileUrl)
{
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();

private static readonly string ClassName = nameof(CommunityPluginSource);

private string latestEtag = "";

private List<UserPlugin> plugins = new();

private static JsonSerializerOptions PluginStoreItemSerializationOption = new JsonSerializerOptions()
private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
Expand All @@ -34,35 +41,49 @@ public record CommunityPluginSource(string ManifestFileUrl)
/// </remarks>
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token)
{
Log.Info(nameof(CommunityPluginSource), $"Loading plugins from {ManifestFileUrl}");
API.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}");

var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl);

request.Headers.Add("If-None-Match", latestEtag);

using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token)
try
{
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token)
.ConfigureAwait(false);

if (response.StatusCode == HttpStatusCode.OK)
{
this.plugins = await response.Content
.ReadFromJsonAsync<List<UserPlugin>>(PluginStoreItemSerializationOption, cancellationToken: token)
.ConfigureAwait(false);
this.latestEtag = response.Headers.ETag?.Tag;
if (response.StatusCode == HttpStatusCode.OK)
{
plugins = await response.Content
.ReadFromJsonAsync<List<UserPlugin>>(PluginStoreItemSerializationOption, cancellationToken: token)
.ConfigureAwait(false);
latestEtag = response.Headers.ETag?.Tag;

Log.Info(nameof(CommunityPluginSource), $"Loaded {this.plugins.Count} plugins from {ManifestFileUrl}");
return this.plugins;
}
else if (response.StatusCode == HttpStatusCode.NotModified)
{
Log.Info(nameof(CommunityPluginSource), $"Resource {ManifestFileUrl} has not been modified.");
return this.plugins;
API.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}");
return plugins;
}
else if (response.StatusCode == HttpStatusCode.NotModified)
{
API.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified.");
return plugins;
}
else
{
API.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
return plugins;
}
}
else
catch (Exception e)
{
Log.Warn(nameof(CommunityPluginSource),
$"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
throw new Exception($"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
{
API.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e);
}
else
{
API.LogException(ClassName, "Error Occurred", e);
}
return plugins;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Windows;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
Expand All @@ -14,6 +13,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
public abstract class AbstractPluginEnvironment
{
private static readonly string ClassName = nameof(AbstractPluginEnvironment);

protected readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();

internal abstract string Language { get; }
Expand Down Expand Up @@ -120,7 +121,7 @@ internal IEnumerable<PluginPair> Setup()
else
{
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
Log.Error("PluginsLoader",
API.LogError(ClassName,
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Flow.Launcher.Core.ExternalPlugins.Environments
{

internal class JavaScriptEnvironment : TypeScriptEnvironment
{
internal override string Language => AllowedLanguage.JavaScript;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Flow.Launcher.Core.ExternalPlugins.Environments
{

internal class JavaScriptV2Environment : TypeScriptV2Environment
{
internal override string Language => AllowedLanguage.JavaScriptV2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.VisualStudio.Threading;

namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
Expand All @@ -30,13 +31,15 @@ internal override string PluginsSettingsFilePath

internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());

internal override void InstallEnvironment()
{
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));

// Python 3.11.4 is no longer Windows 7 compatible. If user is on Win 7 and
// uses Python plugin they need to custom install and use v3.8.9
DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath).Wait();
JTF.Run(() => DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath));

PluginsSettingsFilePath = ExecutablePath;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.VisualStudio.Threading;

namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
Expand All @@ -27,11 +28,13 @@ internal override string PluginsSettingsFilePath

internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());

internal override void InstallEnvironment()
{
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));

DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath));

PluginsSettingsFilePath = ExecutablePath;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.VisualStudio.Threading;

namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
Expand All @@ -27,11 +28,13 @@ internal override string PluginsSettingsFilePath

internal TypeScriptV2Environment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());

internal override void InstallEnvironment()
{
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));

DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath));

PluginsSettingsFilePath = ExecutablePath;
}
Expand Down
4 changes: 3 additions & 1 deletion Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Flow.Launcher.Core.ExternalPlugins
{
public static class PluginsManifest
{
private static readonly string ClassName = nameof(PluginsManifest);

private static readonly CommunityPluginStore mainPluginStore =
new("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/plugin_api_v2/plugins.json",
"https://fastly.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json",
Expand Down Expand Up @@ -44,7 +46,7 @@ public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = fals
}
catch (Exception e)
{
Ioc.Default.GetRequiredService<IPublicAPI>().LogException(nameof(PluginsManifest), "Http request failed", e);
Ioc.Default.GetRequiredService<IPublicAPI>().LogException(ClassName, "Http request failed", e);
}
finally
{
Expand Down
28 changes: 17 additions & 11 deletions Flow.Launcher.Core/Plugin/PluginConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
using System.Linq;
using System.IO;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
using System.Text.Json;
using CommunityToolkit.Mvvm.DependencyInjection;

namespace Flow.Launcher.Core.Plugin
{
internal abstract class PluginConfig
{
private static readonly string ClassName = nameof(PluginConfig);

// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();

/// <summary>
/// Parse plugin metadata in the given directories
/// </summary>
Expand All @@ -32,7 +38,7 @@ public static List<PluginMetadata> Parse(string[] pluginDirectories)
}
catch (Exception e)
{
Log.Exception($"|PluginConfig.ParsePLuginConfigs|Can't delete <{directory}>", e);
API.LogException(ClassName, $"Can't delete <{directory}>", e);
}
}
else
Expand All @@ -49,11 +55,11 @@ public static List<PluginMetadata> Parse(string[] pluginDirectories)

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"));
x => API.LogWarn(ClassName,
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;
}
Expand Down Expand Up @@ -101,7 +107,7 @@ private static PluginMetadata GetPluginMetadata(string pluginDirectory)
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
if (!File.Exists(configPath))
{
Log.Error($"|PluginConfig.GetPluginMetadata|Didn't find config file <{configPath}>");
API.LogError(ClassName, $"Didn't find config file <{configPath}>");
return null;
}

Expand All @@ -117,19 +123,19 @@ private static PluginMetadata GetPluginMetadata(string pluginDirectory)
}
catch (Exception e)
{
Log.Exception($"|PluginConfig.GetPluginMetadata|invalid json for config <{configPath}>", e);
API.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
return null;
}

if (!AllowedLanguage.IsAllowed(metadata.Language))
{
Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>");
API.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>");
return null;
}

if (!File.Exists(metadata.ExecuteFilePath))
{
Log.Error($"|PluginConfig.GetPluginMetadata|execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
API.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
return null;
}

Expand Down
Loading
Loading