-
-
Notifications
You must be signed in to change notification settings - Fork 367
Allows enabling/disabling installed extensions #1275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a13bc1d
4350e91
81df78e
87ac89a
6d53f0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| using FreneticUtilities.FreneticDataSyntax; | ||
| using FreneticUtilities.FreneticDataSyntax; | ||
| using FreneticUtilities.FreneticExtensions; | ||
| using Microsoft.AspNetCore.Html; | ||
| using SwarmUI.Utils; | ||
|
|
@@ -15,6 +15,12 @@ public class ExtensionsManager | |
| /// <summary>Hashset of folder names of all extensions currently loaded.</summary> | ||
| public HashSet<string> LoadedExtensionFolders = []; | ||
|
|
||
| /// <summary>Hashset of folder names of all extensions currently installed (loaded or disabled).</summary> | ||
| public HashSet<string> InstalledExtensionFolders = []; | ||
|
|
||
| /// <summary>Folder names of disabled extensions.</summary> | ||
| public HashSet<string> DisabledExtensions = []; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This field is a duplicate tracker of what's already in settings |
||
|
|
||
| /// <summary>Simple holder of information about extensions available online.</summary> | ||
| public record class ExtensionInfo(string Name, string Author, string License, string Description, string URL, string[] Tags, string FolderName) | ||
| { | ||
|
|
@@ -62,6 +68,9 @@ public async Task PrepExtensions() | |
| string[] extras = Directory.Exists("./src/Extensions") ? [.. Directory.EnumerateDirectories("./src/Extensions/").Select(s => "src/" + s.Replace('\\', '/').AfterLast("/src/"))] : []; | ||
| string[] deleteMe = [.. extras.Where(e => e.TrimEnd('/').EndsWith(".delete"))]; | ||
| extras = [.. extras.Where(e => !e.TrimEnd('/').EndsWith(".delete") && !e.TrimEnd('/').EndsWith(".disable"))]; | ||
| InstalledExtensionFolders = [.. extras.Select(e => e.AfterLast('/'))]; | ||
| HashSet<string> disabledFolders = BuildDisabledExtensionsAndGetDisabledFolders(); | ||
| extras = [.. extras.Where(e => !disabledFolders.Contains(e.AfterLast('/')))]; | ||
| foreach (string deletable in deleteMe) | ||
| { | ||
| try | ||
|
|
@@ -267,4 +276,94 @@ public T GetExtension<T>() where T : Extension | |
| { | ||
| return Extensions.FirstOrDefault(e => e is T) as T; | ||
| } | ||
|
|
||
| /// <summary>Returns folder name from an extension path.</summary> | ||
| public static string GetFolderNameFromPath(string path) | ||
| { | ||
| return path?.Replace('\\', '/').TrimEnd('/').AfterLast('/') ?? ""; | ||
| } | ||
|
|
||
| /// <summary>Returns normalized "src/Extensions/{folderName}/" for a direct child folder match, or null if not found.</summary> | ||
| public static string GetNormalizedExtensionFolderPath(string folderName) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function does nothing |
||
| { | ||
| folderName = folderName?.Trim(); | ||
| if (string.IsNullOrWhiteSpace(folderName)) | ||
| { | ||
| return null; | ||
| } | ||
| string extensionsRoot = Path.GetFullPath(Utilities.CombinePathWithAbsolute(Environment.CurrentDirectory, "src/Extensions")); | ||
| if (!Directory.Exists(extensionsRoot)) | ||
| { | ||
| return null; | ||
| } | ||
| string matchingFolder = Directory.EnumerateDirectories(extensionsRoot) | ||
| .Select(Path.GetFileName) | ||
| .FirstOrDefault(name => string.Equals(name, folderName, StringComparison.OrdinalIgnoreCase)); | ||
| if (string.IsNullOrWhiteSpace(matchingFolder)) | ||
| { | ||
| return null; | ||
| } | ||
| return $"src/Extensions/{matchingFolder}/"; | ||
| } | ||
|
|
||
| /// <summary>Builds <see cref="DisabledExtensions"/> and returns disabled extension folders.</summary> | ||
| public HashSet<string> BuildDisabledExtensionsAndGetDisabledFolders() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is slowly manually rebuilding a list that's already saved? And is redundant |
||
| { | ||
| if (Program.ServerSettings?.Extensions.DisabledExtensions is null) | ||
| { | ||
| Program.ServerSettings.Extensions.DisabledExtensions = []; | ||
| } | ||
| DisabledExtensions = []; | ||
| HashSet<string> disabledFolders = []; | ||
| foreach (string rawFolderName in Program.ServerSettings.Extensions.DisabledExtensions) | ||
| { | ||
| string folderName = rawFolderName?.Trim(); | ||
| if (string.IsNullOrWhiteSpace(folderName) || !InstalledExtensionFolders.Contains(folderName) || !disabledFolders.Add(folderName)) | ||
| { | ||
| continue; | ||
| } | ||
| DisabledExtensions.Add(folderName); | ||
| } | ||
| return disabledFolders; | ||
| } | ||
|
|
||
| /// <summary>Returns disabled extensions for UI display.</summary> | ||
| public IEnumerable<ExtensionInfo> GetDisabledExtensionsForUi() | ||
| { | ||
| foreach (string folderName in DisabledExtensions.OrderBy(e => e, StringComparer.OrdinalIgnoreCase)) | ||
| { | ||
| ExtensionInfo info = KnownExtensions.FirstOrDefault(e => string.Equals(e.FolderName, folderName, StringComparison.OrdinalIgnoreCase)); | ||
| info ??= new ExtensionInfo(folderName, "(Unknown)", "(Unknown)", "(Disabled - restart to load)", "", ["none"], folderName); | ||
| yield return info; | ||
| } | ||
| } | ||
|
|
||
| /// <summary>Removes an extension folder from the disabled list in settings.</summary> | ||
| public bool RemoveDisabledExtensionSetting(string folderName) | ||
| { | ||
| folderName = folderName?.Trim(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all these cleanups and checks aren't needed |
||
| if (string.IsNullOrWhiteSpace(folderName)) | ||
| { | ||
| return false; | ||
| } | ||
| int removed = Program.ServerSettings.Extensions.DisabledExtensions.RemoveAll(f => string.Equals(f, folderName, StringComparison.OrdinalIgnoreCase)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these string.equals ordinal things still do not need to exist as commented before |
||
| DisabledExtensions.RemoveWhere(f => string.Equals(f, folderName, StringComparison.OrdinalIgnoreCase)); | ||
| return removed > 0; | ||
| } | ||
|
|
||
| /// <summary>Adds an extension folder to the disabled list in settings.</summary> | ||
| public bool AddDisabledExtensionSetting(string folderName) | ||
| { | ||
| folderName = folderName?.Trim(); | ||
| if (string.IsNullOrWhiteSpace(folderName) || !InstalledExtensionFolders.Contains(folderName)) | ||
| { | ||
| return false; | ||
| } | ||
| if (!Program.ServerSettings.Extensions.DisabledExtensions.Any(f => string.Equals(f, folderName, StringComparison.OrdinalIgnoreCase))) | ||
| { | ||
| Program.ServerSettings.Extensions.DisabledExtensions.Add(folderName); | ||
| } | ||
| DisabledExtensions.Add(folderName); | ||
| return true; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -233,6 +233,7 @@ | |
| <td>@(ext.ReadmeURL == "" ? "(Missing)": new HtmlString($"<a target=\"_blank\" href=\"{ext.ReadmeURL}\">Here</a>"))</td> | ||
| <td class="@(ext.License == "MIT" ? "" : "ext-danger-license")">@ext.License</td> | ||
| <td> | ||
| <button class="basic-button danger-button translate" onclick="extensionsManager.setExtensionEnabled('@ext.ExtensionName', false, this)">Disable</button> | ||
| @if (ext.CanUpdate) | ||
| { | ||
| <button class="basic-button" onclick="extensionsManager.updateExtension('@ext.ExtensionName', this)">Update</button> | ||
|
|
@@ -241,6 +242,22 @@ | |
| </td> | ||
| </tr> | ||
| } | ||
| @foreach (ExtensionsManager.ExtensionInfo ext in Program.Extensions.GetDisabledExtensionsForUi()) | ||
| { | ||
| <tr> | ||
| <td>@ext.Name</td> | ||
| <td><code>(Disabled)</code></td> | ||
| <td>@ExtensionsManager.HtmlTags(ext.Tags)</td> | ||
| <td>@ext.Author</td> | ||
| <td>@ext.Description</td> | ||
| <td>@(ext.URL == "" ? "(Missing)" : new HtmlString($"<a target=\"_blank\" href=\"{ext.URL}\">Here</a>"))</td> | ||
| <td class="@(ext.License == "MIT" ? "" : "ext-danger-license")">@ext.License</td> | ||
| <td> | ||
| <button class="basic-button translate" onclick="extensionsManager.setExtensionEnabled('@ext.FolderName', true, this)">Enable</button> | ||
| <button class="basic-button" onclick="extensionsManager.uninstallExtension('@ext.FolderName', this)">Uninstall</button> | ||
| </td> | ||
| </tr> | ||
| } | ||
| </table> | ||
| <br> | ||
| <h3>Available Extensions</h3> | ||
|
|
@@ -254,7 +271,7 @@ | |
| <th>License</th> | ||
| <th>Actions</th> | ||
| </tr> | ||
| @foreach (ExtensionsManager.ExtensionInfo ext in Program.Extensions.KnownExtensions.Where(e => !Program.Extensions.LoadedExtensionFolders.Contains(e.FolderName) && !e.Tags.Contains("hidden"))) | ||
| @foreach (ExtensionsManager.ExtensionInfo ext in Program.Extensions.KnownExtensions.Where(e => !Program.Extensions.InstalledExtensionFolders.Contains(e.FolderName) && !e.Tags.Contains("hidden"))) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. put this back |
||
| { | ||
| <tr> | ||
| <td>@ext.Name</td> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a redundant duplicate of already tracked data