Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Flow.Launcher.Core/Plugin/IResultUpdateRegister.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ public interface IResultUpdateRegister
/// </summary>
/// <param name="pair"></param>
void RegisterResultsUpdatedEvent(PluginPair pair);

/// <summary>
/// Unregister a plugin from the results updated event, e.g. before the plugin is unloaded or reloaded.
/// </summary>
/// <param name="pair"></param>
void UnregisterResultsUpdatedEvent(PluginPair pair);
}
34 changes: 34 additions & 0 deletions Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Threading.Tasks;

namespace Flow.Launcher.Core.Plugin
{
Expand All @@ -13,11 +15,43 @@ internal class PluginAssemblyLoader : AssemblyLoadContext
private readonly AssemblyName assemblyName;

internal PluginAssemblyLoader(string assemblyFilePath)
: base(name: Path.GetFileNameWithoutExtension(assemblyFilePath), isCollectible: true)
{
dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath);
assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath));
}

/// <summary>
/// Initiates unload of the given load context and returns a weak reference that can be used to
/// verify the context has actually been collected. Kept in a separate non-inlined method so the
/// caller's stack frame holds no strong reference to the context that would prevent collection.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
internal static WeakReference UnloadAndGetWeakReference(PluginAssemblyLoader loader)
{
var weakReference = new WeakReference(loader);
loader.Unload();
return weakReference;
}

/// <summary>
/// Waits for an unloaded context to be collected. Returns false if the context is still alive
/// after all attempts, which means something (e.g. a cached delegate) is pinning the old assembly.
/// </summary>
internal static async Task<bool> WaitForUnloadAsync(WeakReference weakReference, int maxAttempts = 10)
{
for (var i = 0; i < maxAttempts && weakReference.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (!weakReference.IsAlive) return true;
// ConfigureAwait(false) keeps the blocking GC loop off the UI thread's context
await Task.Delay(100).ConfigureAwait(false);
}

return !weakReference.IsAlive;
}

internal Assembly LoadAssemblyAndDependencies()
{
return LoadFromAssemblyName(assemblyName);
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher.Core/Plugin/PluginConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ internal static (List<PluginMetadata>, List<PluginMetadata>) GetUniqueLatestPlug
return (unique_list, duplicate_list);
}

private static PluginMetadata GetPluginMetadata(string pluginDirectory)
internal static PluginMetadata GetPluginMetadata(string pluginDirectory)
{
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
if (!File.Exists(configPath))
Expand Down
42 changes: 38 additions & 4 deletions Flow.Launcher.Core/Plugin/PluginInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ await DownloadFileAsync(
return; // do not restart on failure
}

if (Settings.AutoRestartAfterChanging)
if (Settings.HotReloadAfterChanging && await PluginManager.ReloadPluginAsync(newPlugin.ID))
{
PublicApi.Instance.ShowMsg(
Localize.installbtn(),
Localize.InstallSuccessHotReload(newPlugin.Name));
}
else if (Settings.AutoRestartAfterChanging)
{
PublicApi.Instance.RestartApp();
}
Expand Down Expand Up @@ -186,7 +192,14 @@ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldP
return; // don not restart on failure
}

if (Settings.AutoRestartAfterChanging)
if (Settings.HotReloadAfterChanging && !PublicApi.Instance.PluginModified(oldPlugin.ID))
{
// The plugin was fully unloaded and its directory deleted, so no restart is needed
PublicApi.Instance.ShowMsg(
Localize.uninstallbtn(),
Localize.UninstallSuccessHotReload(oldPlugin.Name));
}
else if (Settings.AutoRestartAfterChanging)
{
PublicApi.Instance.RestartApp();
}
Expand Down Expand Up @@ -246,7 +259,13 @@ await DownloadFileAsync(
return; // do not restart on failure
}

if (Settings.AutoRestartAfterChanging)
if (Settings.HotReloadAfterChanging && await PluginManager.ReloadPluginAsync(oldPlugin.ID))
{
PublicApi.Instance.ShowMsg(
Localize.updatebtn(),
Localize.UpdateSuccessHotReload(newPlugin.Name));
}
else if (Settings.AutoRestartAfterChanging)
{
PublicApi.Instance.RestartApp();
}
Expand Down Expand Up @@ -328,6 +347,7 @@ where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version
public static async Task UpdateAllPluginsAsync(IEnumerable<PluginUpdateInfo> resultsForUpdate, bool restart)
{
var anyPluginSuccess = false;
var allPluginsHotReloaded = true;
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
{
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
Expand All @@ -343,26 +363,40 @@ await DownloadFileAsync(
// check if user cancelled download before installing plugin
if (cts.IsCancellationRequested)
{
allPluginsHotReloaded = false;
return;
}

if (!await PublicApi.Instance.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath))
{
allPluginsHotReloaded = false;
return;
}

anyPluginSuccess = true;

if (!Settings.HotReloadAfterChanging || !await PluginManager.ReloadPluginAsync(plugin.ID))
{
allPluginsHotReloaded = false;
}
}
catch (Exception e)
{
allPluginsHotReloaded = false;
PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e);
PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin());
}
}));

if (!anyPluginSuccess) return;

if (restart)
if (allPluginsHotReloaded)
{
PublicApi.Instance.ShowMsg(
Localize.updatebtn(),
Localize.PluginsUpdateSuccessHotReload());
}
else if (restart)
{
PublicApi.Instance.RestartApp();
}
Expand Down
Loading
Loading