Fix disabled plugins being re-enabled on restart
This commit is contained in:
@@ -255,6 +255,14 @@ namespace Emby.Server.Implementations.Plugins
|
||||
}
|
||||
|
||||
_plugins.Add(plugin);
|
||||
|
||||
// Updating a disabled plugin must not enable it again.
|
||||
if (plugin.Manifest.Status == PluginStatus.Disabled)
|
||||
{
|
||||
ProcessAlternative(plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
EnablePlugin(plugin);
|
||||
}
|
||||
|
||||
@@ -632,9 +640,10 @@ namespace Emby.Server.Implementations.Plugins
|
||||
return;
|
||||
}
|
||||
|
||||
var predecessor = _plugins.OrderByDescending(p => p.Version)
|
||||
.FirstOrDefault(p => p.Id.Equals(plugin.Id) && p.IsEnabledAndSupported && p.Version != plugin.Version);
|
||||
if (predecessor is not null)
|
||||
var successor = _plugins.FirstOrDefault(p => p.Id.Equals(plugin.Id)
|
||||
&& p.Version > plugin.Version
|
||||
&& (p.IsEnabledAndSupported || p.Manifest.Status == PluginStatus.Disabled));
|
||||
if (successor is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -763,6 +772,8 @@ namespace Emby.Server.Implementations.Plugins
|
||||
var entry = versions[x];
|
||||
if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
lastName = string.Empty;
|
||||
|
||||
if (!TryGetPluginDlls(entry, out var allowedDlls))
|
||||
{
|
||||
_logger.LogError("One or more assembly paths was invalid. Marking plugin {Plugin} as \"Malfunctioned\".", entry.Name);
|
||||
@@ -772,15 +783,18 @@ namespace Emby.Server.Implementations.Plugins
|
||||
|
||||
entry.DllFiles = allowedDlls;
|
||||
|
||||
// Only clean up older versions when this version will actually be loaded.
|
||||
if (entry.IsEnabledAndSupported)
|
||||
{
|
||||
lastName = entry.Name;
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(lastName))
|
||||
{
|
||||
// Unnamed plugin, so there is nothing to match older versions against.
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -891,9 +905,9 @@ namespace Emby.Server.Implementations.Plugins
|
||||
|
||||
if (previousVersion is null)
|
||||
{
|
||||
// This value is memory only - so that the web will show restart required.
|
||||
plugin.Manifest.Status = PluginStatus.Restart;
|
||||
plugin.Manifest.AutoUpdate = false;
|
||||
// Memory only, so that the web will show restart required. The manifest must keep
|
||||
// holding the persisted state, or a later save would write the wrong state to disk.
|
||||
plugin.RestartRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -906,9 +920,7 @@ namespace Emby.Server.Implementations.Plugins
|
||||
_logger.LogError("Unable to supercede version {Version} of {Name}", previousVersion.Version, previousVersion.Name);
|
||||
}
|
||||
|
||||
// This value is memory only - so that the web will show restart required.
|
||||
plugin.Manifest.Status = PluginStatus.Restart;
|
||||
plugin.Manifest.AutoUpdate = false;
|
||||
plugin.RestartRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,8 +500,9 @@ namespace Emby.Server.Implementations.Updates
|
||||
var plugins = _pluginManager.Plugins;
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
// Don't auto update when plugin marked not to, or when it's disabled.
|
||||
if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == PluginStatus.Disabled)
|
||||
// Don't auto update when plugin marked not to, or when it's disabled or pending removal.
|
||||
if (plugin.Manifest?.AutoUpdate == false
|
||||
|| plugin.Manifest?.Status is PluginStatus.Disabled or PluginStatus.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,14 @@ namespace MediaBrowser.Common.Plugins
|
||||
/// </summary>
|
||||
public bool IsEnabledAndSupported => _supported && Manifest.Status >= PluginStatus.Active;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a restart is required for the plugin's state to take effect.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Memory only. <see cref="Manifest"/> holds the state that is persisted to disk.
|
||||
/// </remarks>
|
||||
public bool RestartRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin has a manifest.
|
||||
/// </summary>
|
||||
@@ -108,7 +116,7 @@ namespace MediaBrowser.Common.Plugins
|
||||
public PluginInfo GetPluginInfo()
|
||||
{
|
||||
var inst = Instance?.GetPluginInfo() ?? new PluginInfo(Manifest.Name, Version, Manifest.Description, Manifest.Id, true);
|
||||
inst.Status = Manifest.Status;
|
||||
inst.Status = RestartRequired ? PluginStatus.Restart : Manifest.Status;
|
||||
inst.HasImage = !string.IsNullOrEmpty(Manifest.ImagePath) || !string.IsNullOrEmpty(Manifest.ImageResourceName);
|
||||
return inst;
|
||||
}
|
||||
|
||||
@@ -293,7 +293,84 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
|
||||
Assert.Equal(packageInfo.Versions[0].Version, result.Version);
|
||||
}
|
||||
|
||||
private PackageInfo GenerateTestPackage()
|
||||
[Fact]
|
||||
public async Task DisablePlugin_CatalogRefresh_StaysDisabled()
|
||||
{
|
||||
var pluginRoot = Path.Combine(_tempPath, "plugins");
|
||||
var pluginDir = CreateTestPlugin(pluginRoot, "Disable Me", PluginStatus.Active);
|
||||
|
||||
var pluginManager = new PluginManager(new NullLogger<PluginManager>(), null!, null!, pluginRoot, new Version(1, 0));
|
||||
var plugin = Assert.Single(pluginManager.Plugins);
|
||||
|
||||
pluginManager.DisablePlugin(plugin);
|
||||
|
||||
Assert.Equal(PluginStatus.Disabled, pluginManager.LoadManifest(pluginDir).Manifest.Status);
|
||||
|
||||
// The web shows that a restart is required, but the persisted state must not change.
|
||||
Assert.Equal(PluginStatus.Restart, plugin.GetPluginInfo().Status);
|
||||
Assert.Equal(PluginStatus.Disabled, plugin.Manifest.Status);
|
||||
Assert.True(plugin.Manifest.AutoUpdate);
|
||||
|
||||
// Every catalog fetch rewrites the manifests of installed plugins from the in-memory status.
|
||||
var packageInfo = GenerateTestPackage(plugin.Id);
|
||||
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), pluginDir, plugin.Manifest.Status);
|
||||
|
||||
Assert.Equal(PluginStatus.Disabled, pluginManager.LoadManifest(pluginDir).Manifest.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_DisabledPluginSortingBeforeEnabledPlugin_IsNotDeleted()
|
||||
{
|
||||
var pluginRoot = Path.Combine(_tempPath, "plugins");
|
||||
var disabledDir = CreateTestPlugin(pluginRoot, "AAA Disabled", PluginStatus.Disabled);
|
||||
CreateTestPlugin(pluginRoot, "ZZZ Active", PluginStatus.Active);
|
||||
|
||||
var pluginManager = new PluginManager(new NullLogger<PluginManager>(), null!, null!, pluginRoot, new Version(1, 0));
|
||||
|
||||
Assert.True(Directory.Exists(disabledDir));
|
||||
Assert.Contains(pluginManager.Plugins, p => string.Equals(p.Name, "AAA Disabled", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadAssemblies_DisabledPluginWithSupersededVersion_DoesNotRevertToOldVersion()
|
||||
{
|
||||
var pluginRoot = Path.Combine(_tempPath, "plugins");
|
||||
var id = Guid.NewGuid();
|
||||
var oldDir = CreateTestPlugin(pluginRoot, "Two Versions", PluginStatus.Superseded, new Version(1, 0), id);
|
||||
var newDir = CreateTestPlugin(pluginRoot, "Two Versions_2.0", PluginStatus.Disabled, new Version(2, 0), id, "Two Versions");
|
||||
|
||||
var pluginManager = new PluginManager(new NullLogger<PluginManager>(), null!, null!, pluginRoot, new Version(1, 0));
|
||||
|
||||
Assert.Empty(pluginManager.LoadAssemblies());
|
||||
|
||||
// Neither version may be touched: the old one stays superseded instead of being loaded
|
||||
// as a stand-in for the version the user disabled.
|
||||
Assert.Equal(PluginStatus.Superseded, pluginManager.LoadManifest(oldDir).Manifest.Status);
|
||||
Assert.Equal(PluginStatus.Disabled, pluginManager.LoadManifest(newDir).Manifest.Status);
|
||||
}
|
||||
|
||||
private string CreateTestPlugin(string root, string folderName, PluginStatus status, Version? version = null, Guid? id = null, string? name = null)
|
||||
{
|
||||
var dir = Path.Combine(root, folderName);
|
||||
Directory.CreateDirectory(dir);
|
||||
FileHelper.CreateEmpty(Path.Combine(dir, "some.dll"));
|
||||
|
||||
var manifest = new PluginManifest
|
||||
{
|
||||
Id = id ?? Guid.NewGuid(),
|
||||
Name = name ?? folderName,
|
||||
Status = status,
|
||||
AutoUpdate = true,
|
||||
TargetAbi = "1.0",
|
||||
Version = (version ?? new Version(1, 0)).ToString()
|
||||
};
|
||||
|
||||
File.WriteAllText(Path.Combine(dir, "meta.json"), JsonSerializer.Serialize(manifest, _options));
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
private PackageInfo GenerateTestPackage(Guid? id = null)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
fixture.Customize<PackageInfo>(c => c.Without(x => x.Versions).Without(x => x.ImageUrl));
|
||||
@@ -305,6 +382,10 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
|
||||
|
||||
var packageInfo = fixture.Create<PackageInfo>();
|
||||
packageInfo.Versions = new[] { versionInfo };
|
||||
if (id.HasValue)
|
||||
{
|
||||
packageInfo.Id = id.Value;
|
||||
}
|
||||
|
||||
return packageInfo;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user