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
112 changes: 91 additions & 21 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
private static readonly ConcurrentDictionary<string, PluginPair> _allInitializedPlugins = [];
private static readonly ConcurrentDictionary<string, PluginPair> _initFailedPlugins = [];
private static readonly ConcurrentDictionary<string, PluginPair> _globalPlugins = [];
private static readonly ConcurrentDictionary<string, PluginPair> _nonGlobalPlugins = [];
private static readonly ConcurrentDictionary<string, List<PluginPair>> _nonGlobalPlugins = [];

private static PluginsSettings Settings;
private static readonly ConcurrentBag<string> ModifiedPlugins = [];
Expand Down Expand Up @@ -332,7 +332,19 @@
_globalPlugins.TryAdd(pair.Metadata.ID, pair);
break;
default:
_nonGlobalPlugins.TryAdd(actionKeyword, pair);
_nonGlobalPlugins.AddOrUpdate(actionKeyword,
_ => [pair],
(_, existing) =>
{
lock (existing)
{
if (!existing.Contains(pair))
{
existing.Add(pair);
}
}
return existing;
});
break;
}
}
Expand Down Expand Up @@ -368,21 +380,33 @@
if (query is null)
return Array.Empty<PluginPair>();

if (!_nonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin))
if (!TryGetNonGlobalPlugins(query.ActionKeyword, out var plugins))
{
if (dialogJump)
return [.. GetGlobalPlugins().Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID))];
else
return [.. GetGlobalPlugins().Where(p => !PluginModified(p.Metadata.ID))];
}

if (dialogJump && plugin.Plugin is not IAsyncDialogJump)
return Array.Empty<PluginPair>();
var validPlugins = plugins.Where(p => !p.Metadata.Disabled && !PluginModified(p.Metadata.ID));
if (dialogJump)
validPlugins = validPlugins.Where(p => p.Plugin is IAsyncDialogJump);

if (PluginModified(plugin.Metadata.ID))
return Array.Empty<PluginPair>();
return [.. validPlugins];
}

return [plugin];
private static bool TryGetNonGlobalPlugins(string actionKeyword, out List<PluginPair> plugins)
{
if (_nonGlobalPlugins.TryGetValue(actionKeyword, out var list))
{
lock (list)
{
plugins = [.. list];
}
return true;
}
plugins = [];
return false;
}

public static ICollection<PluginPair> ValidPluginsForHomeQuery()
Expand Down Expand Up @@ -576,9 +600,17 @@
return [.. _globalPlugins.Values];
}

public static Dictionary<string, PluginPair> GetNonGlobalPlugins()
public static Dictionary<string, List<PluginPair>> GetNonGlobalPlugins()
{
return _nonGlobalPlugins.ToDictionary();
var nonGlobalPlugins = new Dictionary<string, List<PluginPair>>();
foreach (var kvp in _nonGlobalPlugins)
{

Check warning on line 607 in Flow.Launcher.Core/Plugin/PluginManager.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`kvp` is not a recognized word. (unrecognized-spelling)
lock (kvp.Value)
{

Check warning on line 609 in Flow.Launcher.Core/Plugin/PluginManager.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`kvp` is not a recognized word. (unrecognized-spelling)
nonGlobalPlugins.Add(kvp.Key, [.. kvp.Value]);
}

Check warning on line 611 in Flow.Launcher.Core/Plugin/PluginManager.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`kvp` is not a recognized word. (unrecognized-spelling)

Check warning on line 611 in Flow.Launcher.Core/Plugin/PluginManager.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`kvp` is not a recognized word. (unrecognized-spelling)
}
return nonGlobalPlugins;
}
Comment thread
Jack251970 marked this conversation as resolved.

public static List<PluginPair> GetTranslationPlugins()
Expand Down Expand Up @@ -721,12 +753,12 @@

#region Plugin Action Keyword

[Obsolete("This method is only used for old Flow compatibility.")]
public static bool ActionKeywordRegistered(string actionKeyword)
{
// this method is only checking for action keywords (defined as not '*') registration
// hence the actionKeyword != Query.GlobalPluginWildcardSign logic
return actionKeyword != Query.GlobalPluginWildcardSign
&& _nonGlobalPlugins.ContainsKey(actionKeyword);
// Since now we support to assign one action keyword to multiple plugins,
// this check is unnecessary, so we will just return false here to ensure compatibility for old plugins.
return false;
}

/// <summary>
Expand All @@ -736,17 +768,34 @@
public static void AddActionKeyword(string id, string newActionKeyword)
{
var plugin = GetPluginForId(id);
if (plugin == null) return;

if (newActionKeyword == Query.GlobalPluginWildcardSign)
{
_globalPlugins.TryAdd(id, plugin);
}
else
{
_nonGlobalPlugins.AddOrUpdate(newActionKeyword, plugin, (key, oldValue) => plugin);
_nonGlobalPlugins.AddOrUpdate(newActionKeyword,
_ => [plugin],
(_, existing) =>
{
lock (existing)
{
if (!existing.Contains(plugin))
{
existing.Add(plugin);
}
}
return existing;
});
Comment thread
Jack251970 marked this conversation as resolved.
}

// Update action keywords and action keyword in plugin metadata
plugin.Metadata.ActionKeywords.Add(newActionKeyword);
if (!plugin.Metadata.ActionKeywords.Contains(newActionKeyword))
{
plugin.Metadata.ActionKeywords.Add(newActionKeyword);
}
if (plugin.Metadata.ActionKeywords.Count > 0)
{
plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0];
Expand All @@ -764,6 +813,8 @@
public static void RemoveActionKeyword(string id, string oldActionkeyword)
{
var plugin = GetPluginForId(id);
if (plugin == null) return;

if (oldActionkeyword == Query.GlobalPluginWildcardSign
&& // Plugins may have multiple ActionKeywords that are global, eg. WebSearch
plugin.Metadata.ActionKeywords
Expand All @@ -774,11 +825,22 @@

if (oldActionkeyword != Query.GlobalPluginWildcardSign)
{
_nonGlobalPlugins.TryRemove(oldActionkeyword, out _);
if (_nonGlobalPlugins.TryGetValue(oldActionkeyword, out var plugins))
{
lock (plugins)
{
plugins.RemoveAll(p => p.Metadata.ID == id);
Comment thread
Jack251970 marked this conversation as resolved.

if (plugins.Count == 0)
{
_nonGlobalPlugins.TryRemove(new KeyValuePair<string, List<PluginPair>>(oldActionkeyword, plugins));
}
}
}
}

// Update action keywords and action keyword in plugin metadata
plugin.Metadata.ActionKeywords.Remove(oldActionkeyword);
plugin.Metadata.ActionKeywords.RemoveAll(k => k == oldActionkeyword);
if (plugin.Metadata.ActionKeywords.Count > 0)
{
plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0];
Expand Down Expand Up @@ -1032,10 +1094,18 @@
{
_globalPlugins.TryRemove(plugin.ID, out var _);
}
var keysToRemove = _nonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList();
foreach (var key in keysToRemove)
var entriesToUpdate = _nonGlobalPlugins.ToList();
foreach (var entry in entriesToUpdate)
{
_nonGlobalPlugins.TryRemove(key, out var _);
lock (entry.Value)
{
entry.Value.RemoveAll(p => p.Metadata.ID == plugin.ID);
Comment thread
Jack251970 marked this conversation as resolved.

if (entry.Value.Count == 0)
{
_nonGlobalPlugins.TryRemove(new KeyValuePair<string, List<PluginPair>>(entry.Key, entry.Value));
}
}
}
}

Expand Down
13 changes: 11 additions & 2 deletions Flow.Launcher.Core/Plugin/QueryBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Flow.Launcher.Plugin;

namespace Flow.Launcher.Core.Plugin
{
public static class QueryBuilder
{
Comment thread
Jack251970 marked this conversation as resolved.
public static Query Build(string originalQuery, string trimmedQuery, Dictionary<string, PluginPair> nonGlobalPlugins)
public static Query Build(string originalQuery, string trimmedQuery, Dictionary<string, List<PluginPair>> nonGlobalPlugins)
{
// home query
if (string.IsNullOrEmpty(trimmedQuery))
Expand Down Expand Up @@ -34,7 +35,7 @@ public static Query Build(string originalQuery, string trimmedQuery, Dictionary<
string possibleActionKeyword = terms[0];
string[] searchTerms;

if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled)
if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPairs) && CheckPlugin(pluginPairs))
{
// use non global plugin for query
actionKeyword = possibleActionKeyword;
Expand All @@ -59,5 +60,13 @@ public static Query Build(string originalQuery, string trimmedQuery, Dictionary<
IsHomeQuery = false
};
}

private static bool CheckPlugin(List<PluginPair> pluginPairs)
{
lock (pluginPairs)
{
return pluginPairs.Any(plugin => !plugin.Metadata.Disabled);
}
}
Comment thread
Jack251970 marked this conversation as resolved.
}
}
5 changes: 5 additions & 0 deletions Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,11 @@ public interface IPublicAPI
/// </summary>
/// <param name="actionKeyword">The actionkeyword for checking</param>
/// <returns>True if the actionkeyword is already assigned, False otherwise</returns>
/// <remarks>
/// Flow now supports assigning one action keyword to multiple plugins.
/// This method is kept only for legacy Flow compatibility.
/// </remarks>
Comment thread
Jack251970 marked this conversation as resolved.
Comment thread
Jack251970 marked this conversation as resolved.
[Obsolete("Flow now supports assigning one action keyword to multiple plugins. This method always returns false for compatibility.")]
bool ActionKeywordAssigned(string actionKeyword);
Comment thread
Jack251970 marked this conversation as resolved.

/// <summary>
Expand Down
10 changes: 5 additions & 5 deletions Flow.Launcher.Test/QueryBuilderTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework.Legacy;

Check warning on line 3 in Flow.Launcher.Test/QueryBuilderTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`NUnit` is not a recognized word. (unrecognized-spelling)
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;

Expand All @@ -11,21 +11,21 @@
[Test]
public void ExclusivePluginQueryTest()
{
var nonGlobalPlugins = new Dictionary<string, PluginPair>
var nonGlobalPlugins = new Dictionary<string, List<PluginPair>>
{
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}}}}
{ ">", new List<PluginPair>(){ new() { Metadata = new PluginMetadata { ActionKeywords = [">"] } } } }
};

Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);

Check warning on line 19 in Flow.Launcher.Test/QueryBuilderTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`google` is not a recognized word. (unrecognized-spelling)

ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.TrimmedQuery);

Check warning on line 21 in Flow.Launcher.Test/QueryBuilderTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`google` is not a recognized word. (unrecognized-spelling)
ClassicAssert.AreEqual("ping google.com -n 20 -6", q.Search, "Search should not start with the ActionKeyword.");

Check warning on line 22 in Flow.Launcher.Test/QueryBuilderTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`google` is not a recognized word. (unrecognized-spelling)
ClassicAssert.AreEqual(">", q.ActionKeyword);

ClassicAssert.AreEqual(5, q.SearchTerms.Length, "The length of SearchTerms should match.");

ClassicAssert.AreEqual("ping", q.FirstSearch);
ClassicAssert.AreEqual("google.com", q.SecondSearch);

Check warning on line 28 in Flow.Launcher.Test/QueryBuilderTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`google` is not a recognized word. (unrecognized-spelling)
ClassicAssert.AreEqual("-n", q.ThirdSearch);

ClassicAssert.AreEqual("google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
Expand All @@ -34,9 +34,9 @@
[Test]
public void ExclusivePluginQueryIgnoreDisabledTest()
{
var nonGlobalPlugins = new Dictionary<string, PluginPair>
var nonGlobalPlugins = new Dictionary<string, List<PluginPair>>
{
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}, Disabled = true}}}
{ ">", new List<PluginPair>(){ new() { Metadata = new PluginMetadata { ActionKeywords = [">"], Disabled = true } } } }
};

Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
Expand All @@ -51,7 +51,7 @@
[Test]
public void GenericPluginQueryTest()
{
Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", new Dictionary<string, PluginPair>());
Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", []);

ClassicAssert.AreEqual("file.txt file2 file3", q.Search);
ClassicAssert.AreEqual("", q.ActionKeyword);
Expand Down
4 changes: 3 additions & 1 deletion Flow.Launcher/PublicAPIInstance.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
Expand Down Expand Up @@ -267,7 +267,9 @@ public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, A
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
PluginManager.AddActionKeyword(pluginId, newActionKeyword);

#pragma warning disable CS0618 // Type or member is obsolete
public bool ActionKeywordAssigned(string actionKeyword) => PluginManager.ActionKeywordRegistered(actionKeyword);
#pragma warning restore CS0618 // Type or member is obsolete

public void RemoveActionKeyword(string pluginId, string oldActionKeyword) =>
PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
Expand Down
Loading