From 7986465cf785ca385fd1765326887e550bced033 Mon Sep 17 00:00:00 2001 From: Greenback Date: Sun, 6 Dec 2020 23:48:54 +0000 Subject: [PATCH 01/77] Initial upload --- .../ApplicationHost.cs | 250 ++----- .../Emby.Server.Implementations.csproj | 8 +- .../Plugins/Active.png | Bin 0 -> 1422 bytes .../Plugins/Malfunction.png | Bin 0 -> 2091 bytes .../Plugins/NotSupported.png | Bin 0 -> 2046 bytes .../Plugins/PluginManager.cs | 674 ++++++++++++++++++ .../Plugins/PluginManifest.cs | 60 -- .../Plugins/RestartRequired.png | Bin 0 -> 1996 bytes .../Plugins/Superceded.png | Bin 0 -> 2136 bytes Emby.Server.Implementations/Plugins/blank.png | Bin 0 -> 120 bytes .../Plugins/disabled.png | Bin 0 -> 1790 bytes .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 2 +- .../Updates/InstallationManager.cs | 456 ++++++------ .../Controllers/DashboardController.cs | 20 +- Jellyfin.Api/Controllers/PackageController.cs | 8 +- Jellyfin.Api/Controllers/PluginsController.cs | 288 ++++++-- Jellyfin.Api/Models/ConfigurationPageInfo.cs | 8 +- MediaBrowser.Common/IApplicationHost.cs | 42 +- MediaBrowser.Common/Plugins/BasePlugin.cs | 15 +- .../Plugins/IHasPluginConfiguration.cs | 33 + MediaBrowser.Common/Plugins/IPlugin.cs | 40 +- MediaBrowser.Common/Plugins/IPluginManager.cs | 86 +++ MediaBrowser.Common/Plugins/LocalPlugin.cs | 94 ++- MediaBrowser.Common/Plugins/PluginManifest.cs | 85 +++ .../Updates/IInstallationManager.cs | 32 +- .../Updates/InstallationEventArgs.cs | 11 +- .../Updates/PluginUninstalledEventArgs.cs | 7 +- .../IServerApplicationHost.cs | 10 - .../Configuration/ServerConfiguration.cs | 10 + MediaBrowser.Model/Plugins/PluginInfo.cs | 39 +- MediaBrowser.Model/Plugins/PluginStatus.cs | 17 + MediaBrowser.Model/Updates/PackageInfo.cs | 43 +- MediaBrowser.Model/Updates/VersionInfo.cs | 42 +- MediaBrowser.sln | 5 +- 34 files changed, 1678 insertions(+), 707 deletions(-) create mode 100644 Emby.Server.Implementations/Plugins/Active.png create mode 100644 Emby.Server.Implementations/Plugins/Malfunction.png create mode 100644 Emby.Server.Implementations/Plugins/NotSupported.png create mode 100644 Emby.Server.Implementations/Plugins/PluginManager.cs delete mode 100644 Emby.Server.Implementations/Plugins/PluginManifest.cs create mode 100644 Emby.Server.Implementations/Plugins/RestartRequired.png create mode 100644 Emby.Server.Implementations/Plugins/Superceded.png create mode 100644 Emby.Server.Implementations/Plugins/blank.png create mode 100644 Emby.Server.Implementations/Plugins/disabled.png create mode 100644 MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs create mode 100644 MediaBrowser.Common/Plugins/IPluginManager.cs create mode 100644 MediaBrowser.Common/Plugins/PluginManifest.cs create mode 100644 MediaBrowser.Model/Plugins/PluginStatus.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index d74ea03520..f88c6c6208 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -34,7 +34,6 @@ using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Playlists; -using Emby.Server.Implementations.Plugins; using Emby.Server.Implementations.QuickConnect; using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; @@ -119,7 +118,9 @@ namespace Emby.Server.Implementations private readonly IXmlSerializer _xmlSerializer; private readonly IJsonSerializer _jsonSerializer; private readonly IStartupOptions _startupOptions; + private readonly IPluginManager _pluginManager; + private List _creatingInstances; private IMediaEncoder _mediaEncoder; private ISessionManager _sessionManager; private string[] _urlPrefixes; @@ -181,16 +182,6 @@ namespace Emby.Server.Implementations protected IServiceCollection ServiceCollection { get; } - private IPlugin[] _plugins; - - private IReadOnlyList _pluginsManifests; - - /// - /// Gets the plugins. - /// - /// The plugins. - public IReadOnlyList Plugins => _plugins; - /// /// Gets the logger factory. /// @@ -294,6 +285,14 @@ namespace Emby.Server.Implementations ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; ApplicationVersionString = ApplicationVersion.ToString(3); ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; + + _pluginManager = new PluginManager( + LoggerFactory, + this, + ServerConfigurationManager.Configuration, + ApplicationPaths.PluginsPath, + ApplicationPaths.CachePath, + ApplicationVersion); } /// @@ -393,8 +392,26 @@ namespace Emby.Server.Implementations /// System.Object. protected object CreateInstanceSafe(Type type) { + if (_creatingInstances == null) + { + _creatingInstances = new List(); + } + + if (_creatingInstances.IndexOf(type) != -1) + { + Logger.LogError("DI Loop detected."); + Logger.LogError("Attempted creation of {Type}", type.FullName); + foreach (var entry in _creatingInstances) + { + Logger.LogError("Called from: {stack}", entry.FullName); + } + + throw new ExternalException("DI Loop detected."); + } + try { + _creatingInstances.Add(type); Logger.LogDebug("Creating instance of {Type}", type); return ActivatorUtilities.CreateInstance(ServiceProvider, type); } @@ -403,6 +420,10 @@ namespace Emby.Server.Implementations Logger.LogError(ex, "Error creating {Type}", type); return null; } + finally + { + _creatingInstances.Remove(type); + } } /// @@ -412,11 +433,7 @@ namespace Emby.Server.Implementations /// ``0. public T Resolve() => ServiceProvider.GetService(); - /// - /// Gets the export types. - /// - /// The type. - /// IEnumerable{Type}. + /// public IEnumerable GetExportTypes() { var currentType = typeof(T); @@ -445,6 +462,27 @@ namespace Emby.Server.Implementations return parts; } + /// + public IReadOnlyCollection GetExports(CreationDelegate defaultFunc, bool manageLifetime = true) + { + // Convert to list so this isn't executed for each iteration + var parts = GetExportTypes() + .Select(i => defaultFunc(i)) + .Where(i => i != null) + .Cast() + .ToList(); + + if (manageLifetime) + { + lock (_disposableParts) + { + _disposableParts.AddRange(parts.OfType()); + } + } + + return parts; + } + /// /// Runs the startup tasks. /// @@ -509,7 +547,7 @@ namespace Emby.Server.Implementations RegisterServices(); - RegisterPluginServices(); + _pluginManager.RegisterServices(ServiceCollection); } /// @@ -523,7 +561,7 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(ConfigurationManager); ServiceCollection.AddSingleton(this); - + ServiceCollection.AddSingleton(_pluginManager); ServiceCollection.AddSingleton(ApplicationPaths); ServiceCollection.AddSingleton(); @@ -768,34 +806,7 @@ namespace Emby.Server.Implementations } ConfigurationManager.AddParts(GetExports()); - _plugins = GetExports() - .Where(i => i != null) - .ToArray(); - - if (Plugins != null) - { - foreach (var plugin in Plugins) - { - if (_pluginsManifests != null && plugin is IPluginAssembly assemblyPlugin) - { - // Ensure the version number matches the Plugin Manifest information. - foreach (var item in _pluginsManifests) - { - if (Path.GetDirectoryName(plugin.AssemblyFilePath).Equals(item.Path, StringComparison.OrdinalIgnoreCase)) - { - // Update version number to that of the manifest. - assemblyPlugin.SetAttributes( - plugin.AssemblyFilePath, - Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(plugin.AssemblyFilePath)), - item.Version); - break; - } - } - } - - Logger.LogInformation("Loaded plugin: {PluginName} {PluginVersion}", plugin.Name, plugin.Version); - } - } + _pluginManager.CreatePlugins(); _urlPrefixes = GetUrlPrefixes().ToArray(); @@ -834,22 +845,6 @@ namespace Emby.Server.Implementations _allConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray(); } - private void RegisterPluginServices() - { - foreach (var pluginServiceRegistrator in GetExportTypes()) - { - try - { - var instance = (IPluginServiceRegistrator)Activator.CreateInstance(pluginServiceRegistrator); - instance.RegisterServices(ServiceCollection); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error registering plugin services from {Assembly}.", pluginServiceRegistrator.Assembly); - } - } - } - private IEnumerable GetTypes(IEnumerable assemblies) { foreach (var ass in assemblies) @@ -862,11 +857,13 @@ namespace Emby.Server.Implementations catch (FileNotFoundException ex) { Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); + _pluginManager.FailPlugin(ass); continue; } catch (TypeLoadException ex) { Logger.LogError(ex, "Error loading types from {Assembly}.", ass.FullName); + _pluginManager.FailPlugin(ass); continue; } @@ -1005,129 +1002,15 @@ namespace Emby.Server.Implementations protected abstract void RestartInternal(); - /// - public IEnumerable GetLocalPlugins(string path, bool cleanup = true) - { - var minimumVersion = new Version(0, 0, 0, 1); - var versions = new List(); - if (!Directory.Exists(path)) - { - // Plugin path doesn't exist, don't try to enumerate subfolders. - return Enumerable.Empty(); - } - - var directories = Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly); - - foreach (var dir in directories) - { - try - { - var metafile = Path.Combine(dir, "meta.json"); - if (File.Exists(metafile)) - { - var manifest = _jsonSerializer.DeserializeFromFile(metafile); - - if (!Version.TryParse(manifest.TargetAbi, out var targetAbi)) - { - targetAbi = minimumVersion; - } - - if (!Version.TryParse(manifest.Version, out var version)) - { - version = minimumVersion; - } - - if (ApplicationVersion >= targetAbi) - { - // Only load Plugins if the plugin is built for this version or below. - versions.Add(new LocalPlugin(manifest.Guid, manifest.Name, version, dir)); - } - } - else - { - // No metafile, so lets see if the folder is versioned. - metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1]; - - int versionIndex = dir.LastIndexOf('_'); - if (versionIndex != -1 && Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version parsedVersion)) - { - // Versioned folder. - versions.Add(new LocalPlugin(Guid.Empty, metafile, parsedVersion, dir)); - } - else - { - // Un-versioned folder - Add it under the path name and version 0.0.0.1. - versions.Add(new LocalPlugin(Guid.Empty, metafile, minimumVersion, dir)); - } - } - } - catch - { - continue; - } - } - - string lastName = string.Empty; - versions.Sort(LocalPlugin.Compare); - // Traverse backwards through the list. - // The first item will be the latest version. - for (int x = versions.Count - 1; x >= 0; x--) - { - if (!string.Equals(lastName, versions[x].Name, StringComparison.OrdinalIgnoreCase)) - { - versions[x].DllFiles.AddRange(Directory.EnumerateFiles(versions[x].Path, "*.dll", SearchOption.AllDirectories)); - lastName = versions[x].Name; - continue; - } - - if (!string.IsNullOrEmpty(lastName) && cleanup) - { - // Attempt a cleanup of old folders. - try - { - Logger.LogDebug("Deleting {Path}", versions[x].Path); - Directory.Delete(versions[x].Path, true); - } - catch (Exception e) - { - Logger.LogWarning(e, "Unable to delete {Path}", versions[x].Path); - } - - versions.RemoveAt(x); - } - } - - return versions; - } - /// /// Gets the composable part assemblies. /// /// IEnumerable{Assembly}. protected IEnumerable GetComposablePartAssemblies() { - if (Directory.Exists(ApplicationPaths.PluginsPath)) + foreach (var p in _pluginManager.LoadAssemblies()) { - _pluginsManifests = GetLocalPlugins(ApplicationPaths.PluginsPath).ToList(); - foreach (var plugin in _pluginsManifests) - { - foreach (var file in plugin.DllFiles) - { - Assembly plugAss; - try - { - plugAss = Assembly.LoadFrom(file); - } - catch (FileLoadException ex) - { - Logger.LogError(ex, "Failed to load assembly {Path}", file); - continue; - } - - Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); - yield return plugAss; - } - } + yield return p; } // Include composable parts in the Model assembly @@ -1369,17 +1252,6 @@ namespace Emby.Server.Implementations } } - /// - /// Removes the plugin. - /// - /// The plugin. - public void RemovePlugin(IPlugin plugin) - { - var list = _plugins.ToList(); - list.Remove(plugin); - _plugins = list.ToArray(); - } - public IEnumerable GetApiPluginAssemblies() { var assemblies = _allConcreteTypes diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 91c4648c62..dd60229825 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -73,6 +73,12 @@ + + + + + + + - diff --git a/Emby.Server.Implementations/Plugins/Active.png b/Emby.Server.Implementations/Plugins/Active.png new file mode 100644 index 0000000000000000000000000000000000000000..3722ee5200f60eb51419a182b8e094c42c6291ec GIT binary patch literal 1422 zcmV;91#$X`P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1tUpBK~z{ry_bD# z6jczwXWrYr-Mzc^+Hy3sRX(I7El3YRB@F=!NDwJSK?5z3hyERUk$Awn+W1lWvaa3p$FjG^TLRcsR?^q0{Y0h8v#%l0YbuKMX0XS(^f?3h z`>dU7o81n&{NL+swibZtVsQySzLIfab@fx#wze~U%0PZ)J`7BDe(F)B<(F;*#jZ(2 z&?-6!e?Zw>cR%MAkqnGGISzDLhts~(5oLFnb?`;>Iuk!iQlzyP{Q(g57J-Rx)8w_1 zg5xWmFK>!!yM|0SHvvYskC;2UIjf}b&?}0qzb`qZIgsk~EcI%RHJ6)1(PvJh!Wa)} z8ePyG=N2UU#^CvBZe=GjzDkV9gA0$&d(6HTm-O}N@bnWS=cKwEo37jpVzl6*sW(gx zno3u>Cnp}a%^!%k4VGNJ8FCP-Y7r))2~~@Z&Yv>vz%<*WBh#l&dL}JV-Co}klEqlP zvvbZtlE{9dvrSuZt8M*Yh{5<9VxeoXT~Ne9gu{WM3EmHu94Vx>U4%D}6=tL-s1^RU zup9Bt&c!{V1hCPqNsD()FK4!Sw;-mo=Awvvu}smt-M=U@%V7hRv0~|oq5+oS(%*_y zuS+QlhK&qUR9ei|65Rxrph<84nfu?B$f`Xc(gpAQ@iM?I=jS4iDocqIW_cc8TYN6GrFKrxKT4}3#OovJ6 z@(nvLOQcv-HIG$ySHd{~#X)xY2l{*M+@jN2u=><%$SK{$8J}mXI>E)LZ8u1hsPV&Bnw;s#(nm_%>zdlAHrdv^~YwYxP|ye_BIj2ici_yGuty*q_N zTp>$X9~(HpB+wN2s`;vU#y0CST?Sy$Iz0Wl=!%t*^o!k7BGo%2zW$R*+iZ)z(_z-% zO>S;3xKFxK)1C@Cqy%*@PWm&-Mj zHM+*I7D-i7%q;(RDL<)-<`&4=+1cSyqeg|Rs=9pL>lSezK`oN*fFh7ibEKzqxj<1-5eykJ1WnUKkqI;m11O~s4u`oVb#-;TtgOto c?kxcS0~w3NKFYLwmjD0&07*qoM6N<$g7!P0tN;K2 literal 0 HcmV?d00001 diff --git a/Emby.Server.Implementations/Plugins/Malfunction.png b/Emby.Server.Implementations/Plugins/Malfunction.png new file mode 100644 index 0000000000000000000000000000000000000000..d4726150eb52857ff3ad25462517be0de9187b47 GIT binary patch literal 2091 zcmV+`2-Nq9P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2f#^0K~z{rwO46u zRM!>$=FPmB@opS@yx|6?nP3~-;DUh~3sV%OB83v9CO_JwL83qCB30@FiNQ75N!q5$ zB9(+BRcX`~l|m4f6E(qLOaLb~v0~!}jPY(}jF+)J#=}cWlT%=EV=c``q~|&WFNkUk@oCKDlOF(@PsQ z&*cv_ZS)ekh2syjrk6KrhkjO+Pn|n);+CXDp0^_NU9rl6yoYze;y(k_t=HLNyo%h7 zPr#mTeTO<~Sao3(&43sMn$40^0Cc`d?Kt`y3plD^j!#D@7)qgzHLSWYie>=4V}w07 zxlJ}-IW~|@3HpP{H&STQH5W+H3X}@sexlu^78e9Fd;ck zUHSQo8#K+`qiI#PFuS|8&nk;G&MzY~Ckv-&5twvM_;2_<6QRIpAJdRYk?iZ!CrP7Z zZ+JLEXZMvZiVUNYl#qJyg|Yzz{mG%wSEH#>7IRlNmOZ`)IR67m_(}0UaQWz481HX` zL7DA?7)Ru$*x(y?p{Tsh!q;hQ=OZy7`Qo!BBSpXa3lNY6WI+)}NJJXn+g>)RE0{djZkfsZ>oBa`!zd~R!adaZNiZ{A?+HdA6IPkA-9 zI^PXkRaj&?82ud2iI{AzF(ai_X#CSJu&T0Na^{p7eSRLAiJvTJWW$0SIm~t)11$E=~_{rc_6fuCx2BvDsV?e=Efc=+1#slnwVhra@ z^xP=Sve%ho6I;YwRwGX^LNf;3mUIIYBOk#@I6E}7RsR?5XD-4uzJ$h<6H$z!bRC5o zE8I9LPZ@A$q#IzMN6iQSh?@7SPgJ~J`}6mX)$MPtI_&JK`3U&t4tmAV3odjcqA`tt z;N#9nv;)T*ojZ@z_xH8*jP>%}O;+~-)gM0@dM7C-$DW+B3K;%0Y{WD$=OdY;5d^6g znvTBl!oJeyObUKtfR+{13}>8f|4+C4q5dl8q`vgSgy_+~X4BUji|mwAzSX{M`<9~D z?VHPhu76QO!h1UdxM_%3%G-_0hyTVt(aY=^khZ)KnJbH-CMILF{R+BHokaie&C*RT zT{tHj$%cdE0rFVneUdz+7z|v2BLh)A4ZtXDVwLp$6H6c2j8(gLW96=0_(oYFRPR}! z=UpUag|Kw%R@mZAr@69443Jn!-OO!+L91$*4A(ZN1zcYN++3!PlxkoI!7GPw{+T`G zy$H4+qg&Ux1Hm~^1IywJXqv_kOl>aA+~`j+EW(lg;Og}u=u1KJ(O4wC=7-vuiP1i0R<=tkhPU5AY$$rI zQi3?%Y;fPEb+*jRlh3QYUg(*!Sc=`Ve8+#1wdFCc9r^E|6MGUQO68<${#S z3R4jg3@~G@CmmScQo?$o=cO^QH!MTz=^-|^hcIzN#h1rFLiHCncJs@CK+M+oOsVtR zzy2|AKE$B3QpuHX7?|j0_(zy2-iEAp47Ogw#NaRj9v_l&GLV{o50_wW$5~NQqO7%! zPaH@)a6jU^E%?#8Nq8BQ|C}&kU`GfYv1j2|ERMg{^;W)ny89&e?&mZe)M3c-8E)V} zhkb-e)(rUk>A3XXQFJ)nr*f&Ljbb@5|=29T>S?D^}fVwY--uTtu zTMkh;9-hOIN7iJmsIaI93wL~vs!p`kUdOjawgI0ratT&=i=?hI0}YT`l_OOZ&m{tERp~LX#HjpQKVfD!D?v^uF;*?;zHkM3 zUByriGeGnlZLE&F1E{<|8_(wCaAO+OMVSPAqz88I8k~Bnn7v(KOH!n!&Ga*lcgeYI z3L-Tx9kmtf{!CSxG)=~uigo)^^V8wJ?n2W42#vdGUk>dOWL>r5=|B%E+`P;V(3yNX zC}qmRTU0%Y#02veYQ;z%Yz@zswJRYlJ~VTJO_LlZ3hm_i)?Zv+%NXnF5Q=MfVMCj0 zR5HEZN&Y3#LHS-k-$=YKMn*7Bm=Lstpcy$YayqQ=qONT#~LO z2NvNyT?K3yV0@8Ao>fhEOoFEl*^u!8fBGObcsr6)Qc7p)wjtliPYIL0AmTch_&>mp Vv#07tKs^8e002ovPDHLkV1ly-^R)l~ literal 0 HcmV?d00001 diff --git a/Emby.Server.Implementations/Plugins/NotSupported.png b/Emby.Server.Implementations/Plugins/NotSupported.png new file mode 100644 index 0000000000000000000000000000000000000000..a13c1f7c1c03605a5d7e0afdc6f4a81621330ab2 GIT binary patch literal 2046 zcmVqliy>cVqq8^Lf84jXzRbXHoxpjmsPn;0Hn?PQ*K6WF7k0Q)&K*4=l2RP#>Du~ z&{=*$nX*rrLCU%10^qLNpR9}9`2}z}miUm-{=~TJI_SOr4E1M4-KiywiL`EFxd8a6 z_NQp#w*M1QZ=!SFht=gCNieu?L-VPk8)+qtNu<;*7XYt{T?uDmpV^e<8$_q?KEMNr zF2i7BJ({X3KgoMv{TeC9mkWT4n@ke=U14fV%#M>lRXnfFXhF;2myuB197%8eBs0qe zKui+QRFu#k`}<=e@wGny`Vj)4BmlqW0B~t>o5XDUQSCmtyL?~my+;KgQ6E%{!EAK1 zSQe%fJ7NYV;?q21^vG zc>u4z2Q>VOG{hR?AWS(3VCb1)Yyfd5UBsppz$}n-3aWPt2tb3M0KgstDa61b3a5*J zYz?`Bf_k!>vRg@qG|r#g?jQgb0ucWZ0oX|Zl)j`KvkO37nyQK2@g_8lHJBLehe)~t zo@>^@EhGr&{SYu{ZCJ4fKo7*ePXKm5I)E#0DKrie2|D_;|2eN7tB43(`FK`@`|lo) zj`v-il?7ZsLWV6iv9SaIE(W6i3iXk{Ail8vkpOt?+ZdjAwsB;*V`$AMRAQ zK_qeD+^Go_(mHp0W8{So&Z{3v)xW^Pi9TG9*V144qpX zfRHSZGU)kOi`(Bbc;dOI;TatTu}o&QYMPmWuB{CN6%|;&{PA{o!izWVsC#)6VoAH z6N%PiM-lLB7{-}?#O$p0SPFpCsX%Oz zfT8>}Ov3}j3W5ExOmhKjHbETt1f)5eDL#(ES?-AVgLS@~3CF94G7M%jJ9dB8xHYS^ z{+pw}jgmeYDBJ7n@AXE+CaVA1N;05d=zcCMoJD9Y9e`(8E?h>tVC5FMaMf=aW74GT->AH~TlQTU?8_})m97r35K-<}eX{`<>{Ty5$^MhApFzLr| zG4V7Mn|}$tsur&2Tb4?D+~2UC;vo&nuP!39zSCDI#d1yjpYo_`*9x@R(C6rmIa2_B zI8%v!%~VzU@Khr=6BW2JIViwI5xp%Nmoh&=+SAFH8|;P4+1e!mOm39JF=!2xN)9RB}ysRiDIQm6WXL5xEja_}ntZlaiKv%Jj0*JLKw+z>SMo?Y9fc3?7(WDv1eEY>U z8-B`z>6aa@nfwE1W$^ZKgN6LhT7zyu2oMGaBFYZ!_(skkm2eEVgYHwmwrB6(G4T=3eS5;c;Os6`Zu=xgl#gn9)Ne$DcJ@IDV;_ zVqm^!hW`2t=5Fa9#54EruF2eX;kNF?r-Ng)LM(B=a?IAIlq85NNcicymckm;m0-{& z3#ed#Ys|0vjCj4!TpMw{*g&*HRnzP>t(v+FbEl76yCY-RtVW(Z*bId?fIPAuAOHXW literal 0 HcmV?d00001 diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs new file mode 100644 index 0000000000..8596dfcf8a --- /dev/null +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -0,0 +1,674 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.Json; +using MediaBrowser.Common; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Json; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Plugins; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations +{ + /// + /// Defines the . + /// + public class PluginManager : IPluginManager + { + private const int OffsetFromTopRightCorner = 38; + + private readonly string _pluginsPath; + private readonly Version _appVersion; + private readonly JsonSerializerOptions _jsonOptions; + private readonly ILogger _logger; + private readonly IApplicationHost _appHost; + private readonly string _imagesPath; + private readonly ServerConfiguration _config; + private readonly IList _plugins; + private readonly Version _nextVersion; + private readonly Version _minimumVersion; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + /// The plugin path. + /// The image cache path. + /// The application version. + public PluginManager( + ILoggerFactory loggerfactory, + IApplicationHost appHost, + ServerConfiguration config, + string pluginsPath, + string imagesPath, + Version appVersion) + { + _logger = loggerfactory.CreateLogger(); + _pluginsPath = pluginsPath; + _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); + _jsonOptions = JsonDefaults.GetOptions(); + _jsonOptions.PropertyNameCaseInsensitive = true; + _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + _config = config; + _appHost = appHost; + _imagesPath = imagesPath; + _nextVersion = new Version(_appVersion.Major, _appVersion.Minor + 2, _appVersion.Build, _appVersion.Revision); + _minimumVersion = new Version(0, 0, 0, 1); + _plugins = Directory.Exists(_pluginsPath) ? DiscoverPlugins().ToList() : new List(); + } + + /// + /// Gets the Plugins. + /// + public IList Plugins => _plugins; + + /// + /// Returns all the assemblies. + /// + /// An IEnumerable{Assembly}. + public IEnumerable LoadAssemblies() + { + foreach (var plugin in _plugins) + { + foreach (var file in plugin.DllFiles) + { + try + { + plugin.Assembly = Assembly.LoadFrom(file); + } + catch (FileLoadException ex) + { + _logger.LogError(ex, "Failed to load assembly {Path}. Disabling plugin.", file); + ChangePluginState(plugin, PluginStatus.Malfunction); + continue; + } + + _logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugin.Assembly.FullName, file); + yield return plugin.Assembly; + } + } + } + + /// + /// Creates all the plugin instances. + /// + public void CreatePlugins() + { + var createdPlugins = _appHost.GetExports(CreatePluginInstance) + .Where(i => i != null) + .ToArray(); + } + + /// + /// Registers the plugin's services with the DI. + /// Note: DI is not yet instantiated yet. + /// + /// A instance. + public void RegisterServices(IServiceCollection serviceCollection) + { + foreach (var pluginServiceRegistrator in _appHost.GetExportTypes()) + { + var plugin = GetPluginByType(pluginServiceRegistrator.Assembly.GetType()); + if (plugin == null) + { + throw new NullReferenceException(); + } + + CheckIfStillSuperceded(plugin); + if (!plugin.IsEnabledAndSupported) + { + continue; + } + + try + { + var instance = (IPluginServiceRegistrator?)Activator.CreateInstance(pluginServiceRegistrator); + instance?.RegisterServices(serviceCollection); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Error registering plugin services from {Assembly}.", pluginServiceRegistrator.Assembly.FullName); + if (ChangePluginState(plugin, PluginStatus.Malfunction)) + { + _logger.LogInformation("Disabling plugin {Path}", plugin.Path); + } + } + } + } + + /// + /// Imports a plugin manifest from . + /// + /// Folder of the plugin. + public void ImportPluginFrom(string folder) + { + if (string.IsNullOrEmpty(folder)) + { + throw new ArgumentNullException(nameof(folder)); + } + + // Load the plugin. + var plugin = LoadManifest(folder); + // Make sure we haven't already loaded this. + if (plugin == null || _plugins.Any(p => p.Manifest.Equals(plugin.Manifest))) + { + return; + } + + _plugins.Add(plugin); + EnablePlugin(plugin); + } + + /// + /// Removes the plugin reference '. + /// + /// The plugin. + /// Outcome of the operation. + public bool RemovePlugin(LocalPlugin plugin) + { + if (plugin == null) + { + throw new ArgumentNullException(nameof(plugin)); + } + + plugin.Instance?.OnUninstalling(); + + if (DeletePlugin(plugin)) + { + return true; + } + + // Unable to delete, so disable. + return ChangePluginState(plugin, PluginStatus.Disabled); + } + + /// + /// Attempts to find the plugin with and id of . + /// + /// Id of plugin. + /// The version of the plugin to locate. + /// A if found, otherwise null. + /// Boolean value signifying the success of the search. + public bool TryGetPlugin(Guid id, Version? version, out LocalPlugin? plugin) + { + if (version == null) + { + // If no version is given, return the largest version number. (This is for backwards compatibility). + plugin = _plugins.Where(p => p.Id.Equals(id)).OrderByDescending(p => p.Version).FirstOrDefault(); + } + else + { + plugin = _plugins.FirstOrDefault(p => p.Id.Equals(id) && p.Version.Equals(version)); + } + + return plugin != null; + } + + /// + /// Enables the plugin, disabling all other versions. + /// + /// The of the plug to disable. + public void EnablePlugin(LocalPlugin plugin) + { + if (plugin == null) + { + throw new ArgumentNullException(nameof(plugin)); + } + + if (ChangePluginState(plugin, PluginStatus.Active)) + { + UpdateSuccessors(plugin); + } + } + + /// + /// Disable the plugin. + /// + /// The of the plug to disable. + public void DisablePlugin(LocalPlugin plugin) + { + if (plugin == null) + { + throw new ArgumentNullException(nameof(plugin)); + } + + // Update the manifest on disk + if (ChangePluginState(plugin, PluginStatus.Disabled)) + { + UpdateSuccessors(plugin); + } + } + + /// + /// Changes the status of the other versions of the plugin to "Superceded". + /// + /// The that's master. + private void UpdateSuccessors(LocalPlugin plugin) + { + // This value is memory only - so that the web will show restart required. + plugin.Manifest.Status = PluginStatus.RestartRequired; + + // Detect whether there is another version of this plugin that needs disabling. + var predecessor = _plugins.OrderByDescending(p => p.Version) + .FirstOrDefault( + p => p.Id.Equals(plugin.Id) + && p.Name.Equals(plugin.Name, StringComparison.OrdinalIgnoreCase) + && p.IsEnabledAndSupported + && p.Version != plugin.Version); + + if (predecessor == null) + { + return; + } + + if (!ChangePluginState(predecessor, PluginStatus.Superceded)) + { + _logger.LogError("Unable to disable version {Version} of {Name}", predecessor.Version, predecessor.Name); + } + } + + /// + /// Disable the plugin. + /// + /// The of the plug to disable. + public void FailPlugin(Assembly assembly) + { + // Only save if disabled. + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + var plugin = _plugins.Where(p => assembly.Equals(p.Assembly)).FirstOrDefault(); + if (plugin == null) + { + // A plugin's assembly didn't cause this issue, so ignore it. + return; + } + + ChangePluginState(plugin, PluginStatus.Malfunction); + } + + /// + /// Saves the manifest back to disk. + /// + /// The to save. + /// The path where to save the manifest. + /// True if successful. + public bool SaveManifest(PluginManifest manifest, string path) + { + if (manifest == null) + { + return false; + } + + try + { + var data = JsonSerializer.Serialize(manifest, _jsonOptions); + File.WriteAllText(Path.Combine(path, "meta.json"), data, Encoding.UTF8); + return true; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception e) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogWarning(e, "Unable to save plugin manifest. {Path}", path); + return false; + } + } + + /// + /// Changes a plugin's load status. + /// + /// The instance. + /// The of the plugin. + /// Success of the task. + private bool ChangePluginState(LocalPlugin plugin, PluginStatus state) + { + if (plugin.Manifest.Status == state || string.IsNullOrEmpty(plugin.Path)) + { + // No need to save as the state hasn't changed. + return true; + } + + plugin.Manifest.Status = state; + SaveManifest(plugin.Manifest, plugin.Path); + try + { + var data = JsonSerializer.Serialize(plugin.Manifest, _jsonOptions); + File.WriteAllText(Path.Combine(plugin.Path, "meta.json"), data, Encoding.UTF8); + return true; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception e) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogWarning(e, "Unable to disable plugin {Path}", plugin.Path); + return false; + } + } + + /// + /// Finds the plugin record using the type. + /// + /// The being sought. + /// The matching record, or null if not found. + private LocalPlugin? GetPluginByType(Type type) + { + // Find which plugin it is by the path. + return _plugins.FirstOrDefault(p => string.Equals(p.Path, Path.GetDirectoryName(type.Assembly.Location), StringComparison.Ordinal)); + } + + /// + /// Creates the instance safe. + /// + /// The type. + /// System.Object. + private object? CreatePluginInstance(Type type) + { + // Find the record for this plugin. + var plugin = GetPluginByType(type); + + if (plugin != null) + { + CheckIfStillSuperceded(plugin); + + if (plugin.IsEnabledAndSupported == true) + { + _logger.LogInformation("Skipping disabled plugin {Version} of {Name} ", plugin.Version, plugin.Name); + return null; + } + } + + try + { + _logger.LogDebug("Creating instance of {Type}", type); + var instance = ActivatorUtilities.CreateInstance(_appHost.ServiceProvider, type); + if (plugin == null) + { + // Create a dummy record for the providers. + var pInstance = (IPlugin)instance; + plugin = new LocalPlugin( + pInstance.AssemblyFilePath, + true, + new PluginManifest + { + Guid = pInstance.Id, + Status = PluginStatus.Active, + Name = pInstance.Name, + Version = pInstance.Version.ToString(), + MaxAbi = _nextVersion.ToString() + }) + { + Instance = pInstance + }; + + _plugins.Add(plugin); + + plugin.Manifest.Status = PluginStatus.Active; + } + else + { + plugin.Instance = (IPlugin)instance; + var manifest = plugin.Manifest; + var pluginStr = plugin.Instance.Version.ToString(); + if (string.Equals(manifest.Version, pluginStr, StringComparison.Ordinal)) + { + // If a plugin without a manifest failed to load due to an external issue (eg config), + // this updates the manifest to the actual plugin values. + manifest.Version = pluginStr; + manifest.Name = plugin.Instance.Name; + manifest.Description = plugin.Instance.Description; + } + + manifest.Status = PluginStatus.Active; + SaveManifest(manifest, plugin.Path); + } + + _logger.LogInformation("Loaded plugin: {PluginName} {PluginVersion}", plugin.Name, plugin.Version); + + return instance; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Error creating {Type}", type.FullName); + if (plugin != null) + { + if (ChangePluginState(plugin, PluginStatus.Malfunction)) + { + _logger.LogInformation("Plugin {Path} has been disabled.", plugin.Path); + return null; + } + } + + _logger.LogDebug("Unable to auto-disable."); + return null; + } + } + + private void CheckIfStillSuperceded(LocalPlugin plugin) + { + if (plugin.Manifest.Status != PluginStatus.Superceded) + { + return; + } + + var predecessor = _plugins.OrderByDescending(p => p.Version) + .FirstOrDefault(p => p.Id.Equals(plugin.Id) && p.IsEnabledAndSupported && p.Version != plugin.Version); + if (predecessor != null) + { + return; + } + + plugin.Manifest.Status = PluginStatus.Active; + } + + /// + /// Attempts to delete a plugin. + /// + /// A instance to delete. + /// True if successful. + private bool DeletePlugin(LocalPlugin plugin) + { + // Attempt a cleanup of old folders. + try + { + _logger.LogDebug("Deleting {Path}", plugin.Path); + Directory.Delete(plugin.Path, true); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception e) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogWarning(e, "Unable to delete {Path}", plugin.Path); + return false; + } + + return _plugins.Remove(plugin); + } + + private LocalPlugin? LoadManifest(string dir) + { + try + { + Version? version; + PluginManifest? manifest = null; + var metafile = Path.Combine(dir, "meta.json"); + if (File.Exists(metafile)) + { + try + { + var data = File.ReadAllText(metafile, Encoding.UTF8); + manifest = JsonSerializer.Deserialize(data, _jsonOptions); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Error deserializing {Path}.", dir); + } + } + + if (manifest != null) + { + if (!Version.TryParse(manifest.TargetAbi, out var targetAbi)) + { + targetAbi = _minimumVersion; + } + + if (!Version.TryParse(manifest.MaxAbi, out var maxAbi)) + { + maxAbi = _appVersion; + } + + if (!Version.TryParse(manifest.Version, out version)) + { + manifest.Version = _minimumVersion.ToString(); + } + + return new LocalPlugin(dir, _appVersion >= targetAbi && _appVersion <= maxAbi, manifest); + } + + // No metafile, so lets see if the folder is versioned. + // TODO: Phase this support out in future versions. + metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1]; + int versionIndex = dir.LastIndexOf('_'); + if (versionIndex != -1) + { + // Get the version number from the filename if possible. + metafile = Path.GetFileName(dir[..versionIndex]) ?? dir[..versionIndex]; + version = Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version? parsedVersion) ? parsedVersion : _appVersion; + } + else + { + // Un-versioned folder - Add it under the path name and version it suitable for this instance. + version = _appVersion; + } + + // Auto-create a plugin manifest, so we can disable it, if it fails to load. + // NOTE: This Plugin is marked as valid for two upgrades, at which point, it can be assumed the + // code base will have changed sufficiently to make it invalid. + manifest = new PluginManifest + { + Status = PluginStatus.RestartRequired, + Name = metafile, + AutoUpdate = false, + Guid = metafile.GetMD5(), + TargetAbi = _appVersion.ToString(), + MaxAbi = _nextVersion.ToString(), + Version = version.ToString() + }; + + return new LocalPlugin(dir, true, manifest); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Something went wrong!"); + return null; + } + } + + /// + /// Gets the list of local plugins. + /// + /// Enumerable of local plugins. + private IEnumerable DiscoverPlugins() + { + var versions = new List(); + + if (!Directory.Exists(_pluginsPath)) + { + // Plugin path doesn't exist, don't try to enumerate sub-folders. + return Enumerable.Empty(); + } + + var directories = Directory.EnumerateDirectories(_pluginsPath, "*.*", SearchOption.TopDirectoryOnly); + LocalPlugin? entry; + foreach (var dir in directories) + { + entry = LoadManifest(dir); + if (entry != null) + { + versions.Add(entry); + } + } + + string lastName = string.Empty; + versions.Sort(LocalPlugin.Compare); + // Traverse backwards through the list. + // The first item will be the latest version. + for (int x = versions.Count - 1; x >= 0; x--) + { + entry = versions[x]; + if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase)) + { + entry.DllFiles.AddRange(Directory.EnumerateFiles(entry.Path, "*.dll", SearchOption.AllDirectories)); + if (entry.IsEnabledAndSupported) + { + lastName = entry.Name; + continue; + } + } + + if (string.IsNullOrEmpty(lastName)) + { + continue; + } + + var manifest = entry.Manifest; + var cleaned = false; + var path = entry.Path; + if (_config.RemoveOldPlugins) + { + // Attempt a cleanup of old folders. + try + { + _logger.LogDebug("Deleting {Path}", path); + Directory.Delete(path, true); + cleaned = true; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception e) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogWarning(e, "Unable to delete {Path}", path); + } + + versions.RemoveAt(x); + } + + if (!cleaned) + { + if (manifest == null) + { + _logger.LogWarning("Unable to disable plugin {Path}", entry.Path); + continue; + } + + // Update the manifest so its not loaded next time. + manifest.Status = PluginStatus.Disabled; + SaveManifest(manifest, entry.Path); + } + } + + // Only want plugin folders which have files. + return versions.Where(p => p.DllFiles.Count != 0); + } + } +} diff --git a/Emby.Server.Implementations/Plugins/PluginManifest.cs b/Emby.Server.Implementations/Plugins/PluginManifest.cs deleted file mode 100644 index 33762791bc..0000000000 --- a/Emby.Server.Implementations/Plugins/PluginManifest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; - -namespace Emby.Server.Implementations.Plugins -{ - /// - /// Defines a Plugin manifest file. - /// - public class PluginManifest - { - /// - /// Gets or sets the category of the plugin. - /// - public string Category { get; set; } - - /// - /// Gets or sets the changelog information. - /// - public string Changelog { get; set; } - - /// - /// Gets or sets the description of the plugin. - /// - public string Description { get; set; } - - /// - /// Gets or sets the Global Unique Identifier for the plugin. - /// - public Guid Guid { get; set; } - - /// - /// Gets or sets the Name of the plugin. - /// - public string Name { get; set; } - - /// - /// Gets or sets an overview of the plugin. - /// - public string Overview { get; set; } - - /// - /// Gets or sets the owner of the plugin. - /// - public string Owner { get; set; } - - /// - /// Gets or sets the compatibility version for the plugin. - /// - public string TargetAbi { get; set; } - - /// - /// Gets or sets the timestamp of the plugin. - /// - public DateTime Timestamp { get; set; } - - /// - /// Gets or sets the Version number of the plugin. - /// - public string Version { get; set; } - } -} diff --git a/Emby.Server.Implementations/Plugins/RestartRequired.png b/Emby.Server.Implementations/Plugins/RestartRequired.png new file mode 100644 index 0000000000000000000000000000000000000000..65fd102a2ca7ffb99c7843fa46b1c5274ec0e76b GIT binary patch literal 1996 zcmV;-2Q&DIP)cX7XQr`GXw;Kd?{+jT}C0~W0?5L5s0=A&*Dm@R@;$vPaQpbjN?|W8G{w&OtDgdf)pX7t*sk0&8fN(`ICCiT>e3u-G%34g3V_$~&5=ixPpnS#T|lR=-_8LD z+mBZ5Y1Eb%|7+EIhBrvrJyQVOJX|CTPo*!>g{OW2l&Lsvf)2X8=MbqmvzTU0BzI>D zfS6=XZDwSXa@{_m_|ke{pq&6z6Mz?40Bl+Ok7U?>Q)QM~uFk5wGhF}@!;bh43|l;` z)`=y}kj{CQKZ4h61*-qRZHfF6^+yk&x4i)Zq0s(o+h1o-9}M<(Lz(s;DI8P?3&6vW z^XMJr-}VDTtO4QJz_2QY<_-BkXF0d#wh&mnn|?CmV#sh+LUFtJaR&Akom=ohadWdH z06scRj8XN*`xLt*(m0U_?UuoT5{Nlrb<7Zn1rtb+b12r#@hN31A@k=5e4E+`e>;SPRoy4XMz&#-P5cK&Q2|$e>55Q3bVkrVUDV<|A2*4%+Q0+^~4u=2?xl4@7)C?H46)?3nK`46w zvjT$Qp;!Pk9stapc2RKzU;t3QM*y}=AHdnSLtWIS{ss0Ax%XfM?dqz*XO9+i#q2GhL^7ABlv-t9VP~C#vLU z6&`ZB#;)3FTm=tcaW-`CW)V-+umEmrHA{0vLMBDPFmr|(L4~o;^2+i&?NxB~1sRDu zZ<%E@vS6!4&$JbaXVxxP?o%Yi11$ygg~R~>N=z%2ec+q8a^!Q|yLl5*krTW_LNF^l z9B_4msj(i-r%KS?byvM?SIy~>)E08P$JCW9J$d2NDa*qP6-jEK?dbFXFhq(nN`Akm zD4~52zGoRiy}WTSZUs@&O*&Q#%)M^7@b%Y2s*FmvsjNwIS1n3h{Zz;aMbZ+W{m03# zKrk{F@Mqx3h$ShB6QT;g#}mJyR^pZrhadkvS78C`VaVSL)wU|nsQ^ez--;;+ijPY6 z3wEQMH1L&Rmp&{Fv%)5)U=mYeF`^3!@JY&B_^j$}cnta=Jt_v6zY=Hj4AyP@%)`S>N6$UW+Qi(`KkfZfh%D6KCCzuX*Vdrb#Q&Ye^xO=LJ3LLq66DmDIsyhf=@`S6_?=PBT7r(OcfIeGojClPedTPn2n8Ix(t{^+ju{0>QOkBV=XU`_gu){t9FEXIljD{RvyGB08yv ztyI##{2~XWr3EPY+2;H;`iv3`-8_J$6oJPn>5OAr2X>X%?3exJ2;WjR*~8*}Is11V zi`EfSO78LmL|SKWG`>@UfNdvN#O*}Ux;4Pn20Hl<4gI485fbmC3W9pf5Fr`;jArj%p>OGvo?OEbi8Y_+Qb zA`Gx?xi`4>eN5tm`){e6w=3oo_!fy}e3i0KoC_N`Og+qe1&kPA_dUQ@!(?NS|5n1! e`^P(H$NvG|Dd5z6`yu`S0000uUe5R3_wKvDci;W} z-U|=?Km4qo543)&6h0O6<}- zBffx*>o77Ip?gq`lApKXdPURNk5jIcn2ZAiKy*1iuyWk&*uggXbHLrb=2)KV(emhJ z6lLtm3yjMPA!Fx20gx!;0~&o+{s^3vQ9kS_K4sj|8y(J{Lv<*&bV`C^5{+vaC;)?% z@quceCHn!@ELvwXOk3{c51r(1xO_17&eQ~jKN%Yb3V`!3U-%uBy*Vq?#hq4Py3q_^ zLJF!B7m=Tnxi2zVwStU~1`2?K*iJY)YweVRiA#R~vgKxLNC67Ay$8SbmtUu^3TkZw z1wcS;Xnw3;g>2z2zMyy!&{0bO@(93(3;?6WeQMp7PPuVGlAyTUrWXYuRBZ~bL$_XR zF!ZLhk!-eu+~EC}H9+25W*5K1C{6zcn%djo@%fgcO~1BIgVCvZ1X;{^8}!2v20*Mz znA{{=l>&6J3HVPYhMA1?eR~H`pJVQG7!AC>m0pcc<L2s;=ZZ zt*>p((iyuuG4adkzXN0LgA3LNsO2kmQ|`jaVoi;2fe&U-Jb}3}o3V?U9=$}61vlz| zpbV(Ce+GGM{t&XNS(b}8<>>6arO>psqOI^6d^bj#K=c4u{%_#Y2Q)v~c~)F-D-I03 z^LQ43*h-%vekK6zK)^u)u#5mGT*=yW;Ls6WOM5eBScgK>u0?BG2Sk;{n3Oc%h64~t z8Q4PQ^c!F|infA^T2o5w&Z9-@dsg1>LjVi}z&DiuEN1|Qr5!#|9VqjJSmJ=R@Ao5& z|0bH-+u(4&1c6^Kaxwu>ZJ45#EnNq-;v8D4D!>=Lgh8XcA(pxWW#0h0dW)>|2cQFx zB}2Jw71ebM?)dG-RR3`(FS~`3(o&SxRAJ5o5BTo=B!C=lnE+hb9IUny3Q<^IfAVKl z-7j8l_QONHB=d()kh_cs4W(g8G_k>=Hu?a-YHR#foQqwIX^YoGey&J zTy%g6O3qGHM z$5Zf=rU6O$qYxc8j?PREq5w!>0hXr+aU~ z@4=18PQ6EIf9Dy&rHEcFp}dk>cXylhWAvv`?ns93R~MZ60$_9Y%YY+Z!IL6fy&ZrC z6~_dN=vG|ty;V;IKVjweEJaZkeyP_1-IC9>Px=tfn|H5 z01_T(DD{tS(i~J`PUE>PI89q{-i=KuQEi;oD4h|6n!M|1t!sopWCMxEs~8a+NM%_F zXlVk0EuAtXY}T)s({9ipd&?;b&ZuS)oKPk!J@DadIn8&C%0_@8KHhMV)5$HehW1V+ z?);jIlLfUa)*UN3VA26)e305_`5ZcG>1HLMbQ?(PJL!_6tpfCPs@l4dvlR8k<AYE*>?W;{2gJ%QVEV5j0WM5Qgnl}-EM^HuH)k&w^(ahW{E&Cw=^^>=m~8~2^B zS(AFU@_+PM%SmyNS{@xq$3`w!Sr)o)EkS43P!t`_MA?JZ%$$;joDY2L6DBXDv=3Qe z5u6H6;eqH)B;}5ipiDdp-`HGF>S$+_=BUo9M#}_QNH`uUpA62JDISB{KNa9~ zPT9`rgN26|O?M4i_0NB7_n5YXZb+Y#$ec1!AxS}0+cK64j$}maLq(R(0Jy+>U$kXK z&2dK0=?y!MjTQjtC~l-HzR^N%dF%q}MU*)S-}SlU*<@$GU3~kE1>0=9D(F8+@o@;M zGAZp+L~Wl;YojeR5Y;4}1R&!vfVw`Ih8_S>VL)>Qhlyuiy_FEUb_ZNLctPVIe_`yd z&%DCs%neVKjPOO*gA!cN%g3dw5ApYv!4QgxCAxOApQAgJJJ0+P08=Rgf1(wrpZt%* zdhG_|MoJFGug)G3yqw`R^WnuP6|IcsQBU#m(6KBiTP5A3%8=jTcyn2(*E0lpO z0&xDt0H_kCsb#bZD$(8mgnXzoZ{gDArn_I?a2xrWb-A9`a(hS0nfs@XF7=%oICbR0 zVKcpYd`-$?g)50U@L6~61v9WQxQuTw+8YeLZ#hhFh`u;pKL)o-u5Lb8 zO(7Slf2cukAd&0w<&)9C=V@jpD#qMz>bTz@05LUpCP;YxS<7bc;3}Fm4a`UOtJIvi z_ov6RYdJgf`~1_wFr%o3R|8Ox*VDx@L?S#n0SJIxRtCl{wb+pB^%Z{LF1Y=(%4aNu&-2;%9(!-o&Y z;&&4Z1wdF>SfEZ!+6MpJxCIbfc~H}6S&ViMfAd5ao<+`S7b%I`pWdPXP?+fhdr3IH2b zl7IDTdvr`3^L|}D=yX~Npt8Ia(lauAalQxDXuIh0<;%j%%uJ1=065pKT~lFl2}pv? z612Hs`9(%;ggd2W)RdHz2rphdg8{V$7>wC>;Pd$~K0XdAVBO}lQ6_AmUK>+k$ zxpL(fHk+M-_$8RY_cR*K{*xz9wvr_T01wq|5)5H9Lipq)(VjhfcK@(*=cRz4AZUHx z=1>?&2-bzKg@(tEXdV%f`1rswzvbZU>wvvBqR*utbk$dAks160S^ca3<8a20JQi5ve_K^$nbbPde8(uHd>6W(P)_E zVjqbQDB4<~v$F#@xXw>11wX%Kbl<){CA@n160qElWBLC!jf%%6#(>M_(oL}QMKHtk zRdaE10Rb{3d5ngKhrpmWfKVg?i9`Y;NK%7CgVS8>yVRFb@xg-!w_zeO&YnGc3Oynt z^9F-KT~=0hx}>B;j>B#Xfc$)ATtYH*_jJv2Esx6s4wns28XBOdr@L06P&^{`6)RS3 z^!Ap-kPsn7(LTWnK;kWdYIz0e7Xr~ViYfw2sgsvZ2NrT7IywaaJ)lx4XT?<@5WTEqP2F3#X-hQLbw611UW_zeQ%M03A|m~wGf8T&l)D}* zd_(#KN<`v(1VCCY@(#=t5xTlsUP)MO4Gz0ts{JAxF1eJKS0js!@irM5m=F4yYaDwE z@~H=sU}$h~+9Z?7LYErh(Zh$@Lx&C#carwYWV6;ICnu+Ni!3^r>*Pe@OD7h)xk1g> z04SIQKp1PL@G$}J)!v1)ef#Fx%pJjh-Ykoz151+vG-(NdbVmW;&xbMre?CY_O(PJ@ z2Nsggo7bc)zWdHyG07*qoM6N<$f?{PtRsaA1 literal 0 HcmV?d00001 diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index 161fa05809..a69380cbb7 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -8,10 +8,10 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Updates; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; -using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index ef346dd5d6..b0a1750bdd 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#nullable enable using System; using System.Collections.Concurrent; @@ -12,7 +12,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; -using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; @@ -41,17 +40,15 @@ namespace Emby.Server.Implementations.Updates private readonly IEventManager _eventManager; private readonly IHttpClientFactory _httpClientFactory; private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly IPluginManager _pluginManager; /// /// Gets the application host. /// /// The application host. private readonly IServerApplicationHost _applicationHost; - private readonly IZipClient _zipClient; - private readonly object _currentInstallationsLock = new object(); /// @@ -64,6 +61,17 @@ namespace Emby.Server.Implementations.Updates /// private readonly ConcurrentBag _completedInstallationsInternal; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . public InstallationManager( ILogger logger, IServerApplicationHost appHost, @@ -71,8 +79,8 @@ namespace Emby.Server.Implementations.Updates IEventManager eventManager, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, - IFileSystem fileSystem, - IZipClient zipClient) + IZipClient zipClient, + IPluginManager pluginManager) { _currentInstallations = new List<(InstallationInfo, CancellationTokenSource)>(); _completedInstallationsInternal = new ConcurrentBag(); @@ -83,16 +91,17 @@ namespace Emby.Server.Implementations.Updates _eventManager = eventManager; _httpClientFactory = httpClientFactory; _config = config; - _fileSystem = fileSystem; _zipClient = zipClient; _jsonSerializerOptions = JsonDefaults.GetOptions(); + _jsonSerializerOptions.PropertyNameCaseInsensitive = true; + _pluginManager = pluginManager; } /// public IEnumerable CompletedInstallations => _completedInstallationsInternal; /// - public async Task> GetPackages(string manifestName, string manifest, CancellationToken cancellationToken = default) + public async Task> GetPackages(string manifestName, string manifest, bool filterIncompatible, CancellationToken cancellationToken = default) { try { @@ -103,13 +112,39 @@ namespace Emby.Server.Implementations.Updates return Array.Empty(); } + var minimumVersion = new Version(0, 0, 0, 1); // Store the repository and repository url with each version, as they may be spread apart. foreach (var entry in packages) { - foreach (var ver in entry.versions) + for (int a = entry.Versions.Count - 1; a >= 0; a--) { - ver.repositoryName = manifestName; - ver.repositoryUrl = manifest; + var ver = entry.Versions[a]; + ver.RepositoryName = manifestName; + ver.RepositoryUrl = manifest; + + if (!filterIncompatible) + { + continue; + } + + if (!Version.TryParse(ver.TargetAbi, out var targetAbi)) + { + targetAbi = minimumVersion; + } + + if (!Version.TryParse(ver.MaxAbi, out var maxAbi)) + { + maxAbi = _applicationHost.ApplicationVersion; + } + + // Only show plugins that fall between targetAbi and maxAbi + if (_applicationHost.ApplicationVersion >= targetAbi && _applicationHost.ApplicationVersion <= maxAbi) + { + continue; + } + + // Not compatible with this version so remove it. + entry.Versions.Remove(ver); } } @@ -132,69 +167,61 @@ namespace Emby.Server.Implementations.Updates } } - private static void MergeSort(IList source, IList dest) - { - int sLength = source.Count - 1; - int dLength = dest.Count; - int s = 0, d = 0; - var sourceVersion = source[0].VersionNumber; - var destVersion = dest[0].VersionNumber; - - while (d < dLength) - { - if (sourceVersion.CompareTo(destVersion) >= 0) - { - if (s < sLength) - { - sourceVersion = source[++s].VersionNumber; - } - else - { - // Append all of destination to the end of source. - while (d < dLength) - { - source.Add(dest[d++]); - } - - break; - } - } - else - { - source.Insert(s++, dest[d++]); - if (d >= dLength) - { - break; - } - - sLength++; - destVersion = dest[d].VersionNumber; - } - } - } - /// public async Task> GetAvailablePackages(CancellationToken cancellationToken = default) { var result = new List(); foreach (RepositoryInfo repository in _config.Configuration.PluginRepositories) { - if (repository.Enabled) + if (repository.Enabled && repository.Url != null) { - // Where repositories have the same content, the details of the first is taken. - foreach (var package in await GetPackages(repository.Name, repository.Url, cancellationToken).ConfigureAwait(true)) + // Where repositories have the same content, the details from the first is taken. + foreach (var package in await GetPackages(repository.Name ?? "Unnamed Repo", repository.Url, true, cancellationToken).ConfigureAwait(true)) { - if (!Guid.TryParse(package.guid, out var packageGuid)) + if (!Guid.TryParse(package.Guid, out var packageGuid)) { // Package doesn't have a valid GUID, skip. continue; } - var existing = FilterPackages(result, package.name, packageGuid).FirstOrDefault(); + var existing = FilterPackages(result, package.Name, packageGuid).FirstOrDefault(); + + // Remove invalid versions from the valid package. + for (var i = package.Versions.Count - 1; i >= 0; i--) + { + var version = package.Versions[i]; + + // Update the manifests, if anything changes. + if (_pluginManager.TryGetPlugin(packageGuid, version.VersionNumber, out LocalPlugin? plugin)) + { + bool noChange = string.Equals(plugin!.Manifest.MaxAbi, version.MaxAbi, StringComparison.Ordinal) + || string.Equals(plugin.Manifest.TargetAbi, version.TargetAbi, StringComparison.Ordinal); + if (!noChange) + { + plugin.Manifest.MaxAbi = version.MaxAbi ?? string.Empty; + plugin.Manifest.TargetAbi = version.TargetAbi ?? string.Empty; + _pluginManager.SaveManifest(plugin.Manifest, plugin.Path); + } + } + + // Remove versions with a target abi that is greater then the current application version. + if ((Version.TryParse(version.TargetAbi, out var targetAbi) && _applicationHost.ApplicationVersion < targetAbi) + || (Version.TryParse(version.MaxAbi, out var maxAbi) && _applicationHost.ApplicationVersion > maxAbi)) + { + package.Versions.RemoveAt(i); + } + } + + // Don't add a package that doesn't have any compatible versions. + if (package.Versions.Count == 0) + { + continue; + } + if (existing != null) { // Assumption is both lists are ordered, so slot these into the correct place. - MergeSort(existing.versions, package.versions); + MergeSortedList(existing.Versions, package.Versions); } else { @@ -210,23 +237,23 @@ namespace Emby.Server.Implementations.Updates /// public IEnumerable FilterPackages( IEnumerable availablePackages, - string name = null, - Guid guid = default, - Version specificVersion = null) + string? name = null, + Guid? guid = default, + Version? specificVersion = null) { if (name != null) { - availablePackages = availablePackages.Where(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase)); + availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); } if (guid != Guid.Empty) { - availablePackages = availablePackages.Where(x => Guid.Parse(x.guid) == guid); + availablePackages = availablePackages.Where(x => Guid.Parse(x.Guid) == guid); } if (specificVersion != null) { - availablePackages = availablePackages.Where(x => x.versions.Where(y => y.VersionNumber.Equals(specificVersion)).Any()); + availablePackages = availablePackages.Where(x => x.Versions.Any(y => y.VersionNumber.Equals(specificVersion))); } return availablePackages; @@ -235,10 +262,10 @@ namespace Emby.Server.Implementations.Updates /// public IEnumerable GetCompatibleVersions( IEnumerable availablePackages, - string name = null, - Guid guid = default, - Version minVersion = null, - Version specificVersion = null) + string? name = null, + Guid? guid = default, + Version? minVersion = null, + Version? specificVersion = null) { var package = FilterPackages(availablePackages, name, guid, specificVersion).FirstOrDefault(); @@ -249,8 +276,9 @@ namespace Emby.Server.Implementations.Updates } var appVer = _applicationHost.ApplicationVersion; - var availableVersions = package.versions - .Where(x => Version.Parse(x.targetAbi) <= appVer); + var availableVersions = package.Versions + .Where(x => (string.IsNullOrEmpty(x.TargetAbi) || Version.Parse(x.TargetAbi) <= appVer) + && (string.IsNullOrEmpty(x.MaxAbi) || Version.Parse(x.MaxAbi) >= appVer)); if (specificVersion != null) { @@ -265,12 +293,12 @@ namespace Emby.Server.Implementations.Updates { yield return new InstallationInfo { - Changelog = v.changelog, - Guid = new Guid(package.guid), - Name = package.name, + Changelog = v.Changelog, + Guid = new Guid(package.Guid), + Name = package.Name, Version = v.VersionNumber, - SourceUrl = v.sourceUrl, - Checksum = v.checksum + SourceUrl = v.SourceUrl, + Checksum = v.Checksum }; } } @@ -282,20 +310,6 @@ namespace Emby.Server.Implementations.Updates return GetAvailablePluginUpdates(catalog); } - private IEnumerable GetAvailablePluginUpdates(IReadOnlyList pluginCatalog) - { - var plugins = _applicationHost.GetLocalPlugins(_appPaths.PluginsPath); - foreach (var plugin in plugins) - { - var compatibleVersions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, minVersion: plugin.Version); - var version = compatibleVersions.FirstOrDefault(y => y.Version > plugin.Version); - if (version != null && CompletedInstallations.All(x => x.Guid != version.Guid)) - { - yield return version; - } - } - } - /// public async Task InstallPackage(InstallationInfo package, CancellationToken cancellationToken) { @@ -373,24 +387,140 @@ namespace Emby.Server.Implementations.Updates } /// - /// Installs the package internal. + /// Uninstalls a plugin. /// - /// The package. - /// The cancellation token. - /// . - private async Task InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken) + /// The to uninstall. + public void UninstallPlugin(LocalPlugin plugin) { - // Set last update time if we were installed before - IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => p.Id == package.Guid) - ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase)); + if (plugin == null) + { + return; + } - // Do the install - await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); + if (plugin.Instance?.CanUninstall == false) + { + _logger.LogWarning("Attempt to delete non removable plugin {0}, ignoring request", plugin.Name); + return; + } - // Do plugin-specific processing - _logger.LogInformation(plugin == null ? "New plugin installed: {0} {1}" : "Plugin updated: {0} {1}", package.Name, package.Version); + plugin.Instance?.OnUninstalling(); - return plugin != null; + // Remove it the quick way for now + _pluginManager.RemovePlugin(plugin); + + _eventManager.Publish(new PluginUninstalledEventArgs(plugin.GetPluginInfo())); + + _applicationHost.NotifyPendingRestart(); + } + + /// + public bool CancelInstallation(Guid id) + { + lock (_currentInstallationsLock) + { + var install = _currentInstallations.Find(x => x.info.Guid == id); + if (install == default((InstallationInfo, CancellationTokenSource))) + { + return false; + } + + install.token.Cancel(); + _currentInstallations.Remove(install); + return true; + } + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources or false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + lock (_currentInstallationsLock) + { + foreach (var (info, token) in _currentInstallations) + { + token.Dispose(); + } + + _currentInstallations.Clear(); + } + } + } + + /// + /// Merges two sorted lists. + /// + /// The source instance to merge. + /// The destination instance to merge with. + private static void MergeSortedList(IList source, IList dest) + { + int sLength = source.Count - 1; + int dLength = dest.Count; + int s = 0, d = 0; + var sourceVersion = source[0].VersionNumber; + var destVersion = dest[0].VersionNumber; + + while (d < dLength) + { + if (sourceVersion.CompareTo(destVersion) >= 0) + { + if (s < sLength) + { + sourceVersion = source[++s].VersionNumber; + } + else + { + // Append all of destination to the end of source. + while (d < dLength) + { + source.Add(dest[d++]); + } + + break; + } + } + else + { + source.Insert(s++, dest[d++]); + if (d >= dLength) + { + break; + } + + sLength++; + destVersion = dest[d].VersionNumber; + } + } + } + + private IEnumerable GetAvailablePluginUpdates(IReadOnlyList pluginCatalog) + { + var plugins = _pluginManager.Plugins; + foreach (var plugin in plugins) + { + if (plugin.Manifest?.AutoUpdate == false) + { + continue; + } + + var compatibleVersions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, minVersion: plugin.Version); + var version = compatibleVersions.FirstOrDefault(y => y.Version > plugin.Version); + + if (version != null && CompletedInstallations.All(x => x.Guid != version.Guid)) + { + yield return version; + } + } } private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken) @@ -434,7 +564,9 @@ namespace Emby.Server.Implementations.Updates { Directory.Delete(targetDir, true); } +#pragma warning disable CA1031 // Do not catch general exception types catch +#pragma warning restore CA1031 // Do not catch general exception types { // Ignore any exceptions. } @@ -442,119 +574,27 @@ namespace Emby.Server.Implementations.Updates stream.Position = 0; _zipClient.ExtractAllFromZip(stream, targetDir, true); - -#pragma warning restore CA5351 + _pluginManager.ImportPluginFrom(targetDir); } - /// - /// Uninstalls a plugin. - /// - /// The plugin. - public void UninstallPlugin(IPlugin plugin) - { - if (!plugin.CanUninstall) - { - _logger.LogWarning("Attempt to delete non removable plugin {0}, ignoring request", plugin.Name); - return; - } - - plugin.OnUninstalling(); - - // Remove it the quick way for now - _applicationHost.RemovePlugin(plugin); - - var path = plugin.AssemblyFilePath; - bool isDirectory = false; - // Check if we have a plugin directory we should remove too - if (Path.GetDirectoryName(plugin.AssemblyFilePath) != _appPaths.PluginsPath) - { - path = Path.GetDirectoryName(plugin.AssemblyFilePath); - isDirectory = true; - } - - // Make this case-insensitive to account for possible incorrect assembly naming - var file = _fileSystem.GetFilePaths(Path.GetDirectoryName(path)) - .FirstOrDefault(i => string.Equals(i, path, StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrWhiteSpace(file)) - { - path = file; - } - - try - { - if (isDirectory) - { - _logger.LogInformation("Deleting plugin directory {0}", path); - Directory.Delete(path, true); - } - else - { - _logger.LogInformation("Deleting plugin file {0}", path); - _fileSystem.DeleteFile(path); - } - } - catch - { - // Ignore file errors. - } - - var list = _config.Configuration.UninstalledPlugins.ToList(); - var filename = Path.GetFileName(path); - if (!list.Contains(filename, StringComparer.OrdinalIgnoreCase)) - { - list.Add(filename); - _config.Configuration.UninstalledPlugins = list.ToArray(); - _config.SaveConfiguration(); - } - - _eventManager.Publish(new PluginUninstalledEventArgs(plugin)); - - _applicationHost.NotifyPendingRestart(); - } - - /// - public bool CancelInstallation(Guid id) + private async Task InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken) { - lock (_currentInstallationsLock) + // Set last update time if we were installed before + LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Guid) && p.Version.Equals(package.Version)) + ?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version)); + if (plugin != null) { - var install = _currentInstallations.Find(x => x.info.Guid == id); - if (install == default((InstallationInfo, CancellationTokenSource))) - { - return false; - } - - install.token.Cancel(); - _currentInstallations.Remove(install); - return true; + plugin.Manifest.Timestamp = DateTime.UtcNow; + _pluginManager.SaveManifest(plugin.Manifest, plugin.Path); } - } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Do the install + await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources or false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - lock (_currentInstallationsLock) - { - foreach (var tuple in _currentInstallations) - { - tuple.token.Dispose(); - } + // Do plugin-specific processing + _logger.LogInformation(plugin == null ? "New plugin installed: {0} {1}" : "Plugin updated: {0} {1}", package.Name, package.Version); - _currentInstallations.Clear(); - } - } + return plugin != null; } } } diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index ccc81dfc55..b77d79209a 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -29,18 +29,22 @@ namespace Jellyfin.Api.Controllers { private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; + private readonly IPluginManager _pluginManager; /// /// Initializes a new instance of the class. /// /// Instance of interface. /// Instance of interface. + /// Instance of interface. public DashboardController( ILogger logger, - IServerApplicationHost appHost) + IServerApplicationHost appHost, + IPluginManager pluginManager) { _logger = logger; _appHost = appHost; + _pluginManager = pluginManager; } /// @@ -83,7 +87,7 @@ namespace Jellyfin.Api.Controllers .Where(i => i != null) .ToList(); - configPages.AddRange(_appHost.Plugins.SelectMany(GetConfigPages)); + configPages.AddRange(_pluginManager.Plugins.SelectMany(GetConfigPages)); if (pageType.HasValue) { @@ -155,24 +159,24 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - private IEnumerable GetConfigPages(IPlugin plugin) + private IEnumerable GetConfigPages(LocalPlugin plugin) { - return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1)); + return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin.Instance, i.Item1)); } - private IEnumerable> GetPluginPages(IPlugin plugin) + private IEnumerable> GetPluginPages(LocalPlugin? plugin) { - if (!(plugin is IHasWebPages hasWebPages)) + if (plugin?.Instance is not IHasWebPages hasWebPages) { return new List>(); } - return hasWebPages.GetPages().Select(i => new Tuple(i, plugin)); + return hasWebPages.GetPages().Select(i => new Tuple(i, plugin.Instance)); } private IEnumerable> GetPluginPages() { - return _appHost.Plugins.SelectMany(GetPluginPages); + return _pluginManager.Plugins.SelectMany(GetPluginPages); } } } diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index 6295dfc05d..622a0fe00d 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -2,8 +2,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Net.Mime; using System.Threading.Tasks; +using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; +using MediaBrowser.Common.Json; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Updates; @@ -43,6 +46,7 @@ namespace Jellyfin.Api.Controllers /// A containing package information. [HttpGet("Packages/{name}")] [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(JsonDefaults.CamelCaseMediaType)] public async Task> GetPackageInfo( [FromRoute, Required] string name, [FromQuery] Guid? assemblyGuid) @@ -69,6 +73,7 @@ namespace Jellyfin.Api.Controllers /// An containing available packages information. [HttpGet("Packages")] [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(JsonDefaults.CamelCaseMediaType)] public async Task> GetPackages() { IEnumerable packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); @@ -99,7 +104,7 @@ namespace Jellyfin.Api.Controllers var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); if (!string.IsNullOrEmpty(repositoryUrl)) { - packages = packages.Where(p => p.versions.Where(q => q.repositoryUrl.Equals(repositoryUrl, StringComparison.OrdinalIgnoreCase)).Any()) + packages = packages.Where(p => p.Versions.Any(q => q.RepositoryUrl.Equals(repositoryUrl, StringComparison.OrdinalIgnoreCase))) .ToList(); } @@ -143,6 +148,7 @@ namespace Jellyfin.Api.Controllers /// An containing the list of package repositories. [HttpGet("Repositories")] [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(JsonDefaults.CamelCaseMediaType)] public ActionResult> GetRepositories() { return _serverConfigurationManager.Configuration.PluginRepositories; diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 98f1bc2d23..3f366dd79f 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -1,15 +1,22 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.IO; using System.Linq; +using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; +using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Models.PluginDtos; -using MediaBrowser.Common; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Json; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -23,22 +30,81 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] public class PluginsController : BaseJellyfinApiController { - private readonly IApplicationHost _appHost; private readonly IInstallationManager _installationManager; - - private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.GetOptions(); + private readonly IPluginManager _pluginManager; + private readonly IConfigurationManager _config; + private readonly JsonSerializerOptions _serializerOptions; /// /// Initializes a new instance of the class. /// - /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. public PluginsController( - IApplicationHost appHost, - IInstallationManager installationManager) + IInstallationManager installationManager, + IPluginManager pluginManager, + IConfigurationManager config) { - _appHost = appHost; _installationManager = installationManager; + _pluginManager = pluginManager; + _serializerOptions = JsonDefaults.GetOptions(); + _config = config; + } + + /// + /// Get plugin security info. + /// + /// Plugin security info returned. + /// Plugin security info. + [Obsolete("This endpoint should not be used.")] + [HttpGet("SecurityInfo")] + [ProducesResponseType(StatusCodes.Status200OK)] + public static ActionResult GetPluginSecurityInfo() + { + return new PluginSecurityInfo + { + IsMbSupporter = true, + SupporterKey = "IAmTotallyLegit" + }; + } + + /// + /// Gets registration status for a feature. + /// + /// Feature name. + /// Registration status returned. + /// Mb registration record. + [Obsolete("This endpoint should not be used.")] + [HttpPost("RegistrationRecords/{name}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public static ActionResult GetRegistrationStatus([FromRoute, Required] string name) + { + return new MBRegistrationRecord + { + IsRegistered = true, + RegChecked = true, + TrialVersion = false, + IsValid = true, + RegError = false + }; + } + + /// + /// Gets registration status for a feature. + /// + /// Feature name. + /// Not implemented. + /// Not Implemented. + /// This endpoint is not implemented. + [Obsolete("Paid plugins are not supported")] + [HttpGet("Registrations/{name}")] + [ProducesResponseType(StatusCodes.Status501NotImplemented)] + public static ActionResult GetRegistration([FromRoute, Required] string name) + { + // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins, + // delete all these registration endpoints. They are only kept for compatibility. + throw new NotImplementedException(); } /// @@ -48,15 +114,65 @@ namespace Jellyfin.Api.Controllers /// List of currently installed plugins. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesFile(MediaTypeNames.Application.Json)] public ActionResult> GetPlugins() { - return Ok(_appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo())); + return Ok(_pluginManager.Plugins + .OrderBy(p => p.Name) + .Select(p => p.GetPluginInfo())); + } + + /// + /// Enables a disabled plugin. + /// + /// Plugin id. + /// Plugin version. + /// Plugin enabled. + /// Plugin not found. + /// An on success, or a if the file could not be found. + [HttpPost("{pluginId}/Enable")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult EnablePlugin([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + { + if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) + { + return NotFound(); + } + + _pluginManager.EnablePlugin(plugin!); + return NoContent(); + } + + /// + /// Disable a plugin. + /// + /// Plugin id. + /// Plugin version. + /// Plugin disabled. + /// Plugin not found. + /// An on success, or a if the file could not be found. + [HttpPost("{pluginId}/Disable")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DisablePlugin([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + { + if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) + { + return NotFound(); + } + + _pluginManager.DisablePlugin(plugin!); + return NoContent(); } /// /// Uninstalls a plugin. /// /// Plugin id. + /// Plugin version. /// Plugin uninstalled. /// Plugin not found. /// An on success, or a if the file could not be found. @@ -64,15 +180,14 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId) + public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId, Version version) { - var plugin = _appHost.Plugins.FirstOrDefault(p => p.Id == pluginId); - if (plugin == null) + if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { return NotFound(); } - _installationManager.UninstallPlugin(plugin); + _installationManager.UninstallPlugin(plugin!); return NoContent(); } @@ -80,20 +195,23 @@ namespace Jellyfin.Api.Controllers /// Gets plugin configuration. /// /// Plugin id. + /// Plugin version. /// Plugin configuration returned. /// Plugin not found or plugin configuration not found. /// Plugin configuration. [HttpGet("{pluginId}/Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetPluginConfiguration([FromRoute, Required] Guid pluginId) + [ProducesFile(MediaTypeNames.Application.Json)] + public ActionResult GetPluginConfiguration([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) { - if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin)) + if (_pluginManager.TryGetPlugin(pluginId, version, out var plugin) + && plugin!.Instance is IHasPluginConfiguration configPlugin) { - return NotFound(); + return configPlugin.Configuration; } - return plugin.Configuration; + return NotFound(); } /// @@ -103,6 +221,7 @@ namespace Jellyfin.Api.Controllers /// Accepts plugin configuration as JSON body. /// /// Plugin id. + /// Plugin version. /// Plugin configuration updated. /// Plugin not found or plugin does not have configuration. /// @@ -113,92 +232,125 @@ namespace Jellyfin.Api.Controllers [HttpPost("{pluginId}/Configuration")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task UpdatePluginConfiguration([FromRoute, Required] Guid pluginId) + public async Task UpdatePluginConfiguration([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) { - if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin)) + if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin) + || plugin?.Instance is not IHasPluginConfiguration configPlugin) { return NotFound(); } - var configuration = (BasePluginConfiguration?)await JsonSerializer.DeserializeAsync(Request.Body, plugin.ConfigurationType, _serializerOptions) + var configuration = (BasePluginConfiguration?)await JsonSerializer.DeserializeAsync(Request.Body, configPlugin.ConfigurationType, _serializerOptions) .ConfigureAwait(false); if (configuration != null) { - plugin.UpdateConfiguration(configuration); + configPlugin.UpdateConfiguration(configuration); } return NoContent(); } /// - /// Get plugin security info. + /// Gets a plugin's image. /// - /// Plugin security info returned. - /// Plugin security info. - [Obsolete("This endpoint should not be used.")] - [HttpGet("SecurityInfo")] + /// Plugin id. + /// Plugin version. + /// Plugin image returned. + /// Plugin's image. + [HttpGet("{pluginId}/Image")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetPluginSecurityInfo() + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + [AllowAnonymous] + public ActionResult GetPluginImage([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) { - return new PluginSecurityInfo + if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { - IsMbSupporter = true, - SupporterKey = "IAmTotallyLegit" - }; + return NotFound(); + } + + var imgPath = Path.Combine(plugin!.Path, plugin!.Manifest.ImageUrl ?? string.Empty); + if (((ServerConfiguration)_config.CommonConfiguration).DisablePluginImages + || plugin!.Manifest.ImageUrl == null + || !System.IO.File.Exists(imgPath)) + { + // Use a blank image. + var type = GetType(); + var stream = type.Assembly.GetManifestResourceStream(type.Namespace + ".Plugins.blank.png"); + return File(stream, "image/png"); + } + + imgPath = Path.Combine(plugin.Path, plugin.Manifest.ImageUrl); + return PhysicalFile(imgPath, MimeTypes.GetMimeType(imgPath)); } /// - /// Updates plugin security info. + /// Gets a plugin's status image. /// - /// Plugin security info. - /// Plugin security info updated. - /// An . - [Obsolete("This endpoint should not be used.")] - [HttpPost("SecurityInfo")] - [Authorize(Policy = Policies.RequiresElevation)] - [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdatePluginSecurityInfo([FromBody, Required] PluginSecurityInfo pluginSecurityInfo) + /// Plugin id. + /// Plugin version. + /// Plugin image returned. + /// Plugin's image. + [HttpGet("{pluginId}/StatusImage")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + [AllowAnonymous] + public ActionResult GetPluginStatusImage([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) { - return NoContent(); + if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) + { + return NotFound(); + } + + // Icons from http://www.fatcow.com/free-icons + var status = plugin!.Manifest.Status; + + var type = _pluginManager.GetType(); + var stream = type.Assembly.GetManifestResourceStream($"{type.Namespace}.Plugins.{status}.png"); + return File(stream, "image/png"); } /// - /// Gets registration status for a feature. + /// Gets a plugin's manifest. /// - /// Feature name. - /// Registration status returned. - /// Mb registration record. - [Obsolete("This endpoint should not be used.")] - [HttpPost("RegistrationRecords/{name}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetRegistrationStatus([FromRoute, Required] string name) + /// Plugin id. + /// Plugin version. + /// Plugin manifest returned. + /// Plugin not found. + /// + /// A that represents the asynchronous operation to get the plugin's manifest. + /// The task result contains an indicating success, or + /// when plugin not found. + /// + [HttpPost("{pluginId}/Manifest")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesFile(MediaTypeNames.Application.Json)] + public ActionResult GetPluginManifest([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) { - return new MBRegistrationRecord + if (_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { - IsRegistered = true, - RegChecked = true, - TrialVersion = false, - IsValid = true, - RegError = false - }; + return Ok(plugin!.Manifest); + } + + return NotFound(); } /// - /// Gets registration status for a feature. + /// Updates plugin security info. /// - /// Feature name. - /// Not implemented. - /// Not Implemented. - /// This endpoint is not implemented. - [Obsolete("Paid plugins are not supported")] - [HttpGet("Registrations/{name}")] - [ProducesResponseType(StatusCodes.Status501NotImplemented)] - public ActionResult GetRegistration([FromRoute, Required] string name) + /// Plugin security info. + /// Plugin security info updated. + /// An . + [Obsolete("This endpoint should not be used.")] + [HttpPost("SecurityInfo")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult UpdatePluginSecurityInfo([FromBody, Required] PluginSecurityInfo pluginSecurityInfo) { - // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins, - // delete all these registration endpoints. They are only kept for compatibility. - throw new NotImplementedException(); + return NoContent(); } } } diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index 2aa6373aa9..155e116a56 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Plugins; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; @@ -32,16 +32,16 @@ namespace Jellyfin.Api.Models /// /// Instance of interface. /// Instance of interface. - public ConfigurationPageInfo(IPlugin plugin, PluginPageInfo page) + public ConfigurationPageInfo(IPlugin? plugin, PluginPageInfo page) { Name = page.Name; EnableInMainMenu = page.EnableInMainMenu; MenuSection = page.MenuSection; MenuIcon = page.MenuIcon; - DisplayName = string.IsNullOrWhiteSpace(page.DisplayName) ? plugin.Name : page.DisplayName; + DisplayName = string.IsNullOrWhiteSpace(page.DisplayName) ? plugin?.Name ?? page.DisplayName : page.DisplayName; // Don't use "N" because it needs to match Plugin.Id - PluginId = plugin.Id.ToString(); + PluginId = plugin?.Id.ToString(); } /// diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 849037ac46..ddcf2ac171 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -2,11 +2,16 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; -using MediaBrowser.Common.Plugins; -using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common { + /// + /// Delegate used with GetExports{T}. + /// + /// Type to create. + /// New instance of type type. + public delegate object CreationDelegate(Type type); + /// /// An interface to be implemented by the applications hosting a kernel. /// @@ -53,6 +58,11 @@ namespace MediaBrowser.Common /// The application version. Version ApplicationVersion { get; } + /// + /// Gets or sets the service provider. + /// + IServiceProvider ServiceProvider { get; set; } + /// /// Gets the application version. /// @@ -71,12 +81,6 @@ namespace MediaBrowser.Common /// string ApplicationUserAgentAddress { get; } - /// - /// Gets the plugins. - /// - /// The plugins. - IReadOnlyList Plugins { get; } - /// /// Gets all plugin assemblies which implement a custom rest api. /// @@ -101,6 +105,22 @@ namespace MediaBrowser.Common /// . IReadOnlyCollection GetExports(bool manageLifetime = true); + /// + /// Gets the exports. + /// + /// The type. + /// Delegate function that gets called to create the object. + /// If set to true [manage lifetime]. + /// . + IReadOnlyCollection GetExports(CreationDelegate defaultFunc, bool manageLifetime = true); + + /// + /// Gets the export types. + /// + /// The type. + /// IEnumerable{Type}. + IEnumerable GetExportTypes(); + /// /// Resolves this instance. /// @@ -114,12 +134,6 @@ namespace MediaBrowser.Common /// A task. Task Shutdown(); - /// - /// Removes the plugin. - /// - /// The plugin. - void RemovePlugin(IPlugin plugin); - /// /// Initializes this instance. /// diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 084e91d500..b918fc4f6d 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common.Plugins { @@ -64,14 +63,12 @@ namespace MediaBrowser.Common.Plugins /// PluginInfo. public virtual PluginInfo GetPluginInfo() { - var info = new PluginInfo - { - Name = Name, - Version = Version.ToString(), - Description = Description, - Id = Id.ToString(), - CanUninstall = CanUninstall - }; + var info = new PluginInfo( + Name, + Version, + Description, + Id, + CanUninstall); return info; } diff --git a/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs b/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs new file mode 100644 index 0000000000..42ad85dd37 --- /dev/null +++ b/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs @@ -0,0 +1,33 @@ +using System; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Defines the . + /// + public interface IHasPluginConfiguration + { + /// + /// Gets the type of configuration this plugin uses. + /// + Type ConfigurationType { get; } + + /// + /// Gets the plugin's configuration. + /// + BasePluginConfiguration Configuration { get; } + + /// + /// Completely overwrites the current configuration with a new copy. + /// + /// The configuration. + void UpdateConfiguration(BasePluginConfiguration configuration); + + /// + /// Sets the startup directory creation function. + /// + /// The directory function used to create the configuration folder. + void SetStartupInfo(Action directoryCreateFn); + } +} diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs index d583a58878..b2ba1179c1 100644 --- a/MediaBrowser.Common/Plugins/IPlugin.cs +++ b/MediaBrowser.Common/Plugins/IPlugin.cs @@ -1,44 +1,36 @@ -#pragma warning disable CS1591 - using System; using MediaBrowser.Model.Plugins; -using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common.Plugins { /// - /// Interface IPlugin. + /// Defines the . /// public interface IPlugin { /// /// Gets the name of the plugin. /// - /// The name. string Name { get; } /// - /// Gets the description. + /// Gets the Description. /// - /// The description. string Description { get; } /// /// Gets the unique id. /// - /// The unique id. Guid Id { get; } /// /// Gets the plugin version. /// - /// The version. Version Version { get; } /// /// Gets the path to the assembly file. /// - /// The assembly file path. string AssemblyFilePath { get; } /// @@ -49,11 +41,10 @@ namespace MediaBrowser.Common.Plugins /// /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed. /// - /// The data folder path. string DataFolderPath { get; } /// - /// Gets the plugin info. + /// Gets the . /// /// PluginInfo. PluginInfo GetPluginInfo(); @@ -63,29 +54,4 @@ namespace MediaBrowser.Common.Plugins /// void OnUninstalling(); } - - public interface IHasPluginConfiguration - { - /// - /// Gets the type of configuration this plugin uses. - /// - /// The type of the configuration. - Type ConfigurationType { get; } - - /// - /// Gets the plugin's configuration. - /// - /// The configuration. - BasePluginConfiguration Configuration { get; } - - /// - /// Completely overwrites the current configuration with a new copy - /// Returns true or false indicating success or failure. - /// - /// The configuration. - /// configuration is null. - void UpdateConfiguration(BasePluginConfiguration configuration); - - void SetStartupInfo(Action directoryCreateFn); - } } diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs new file mode 100644 index 0000000000..071b51969f --- /dev/null +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -0,0 +1,86 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Defines the . + /// + public interface IPluginManager + { + /// + /// Gets the Plugins. + /// + IList Plugins { get; } + + /// + /// Creates the plugins. + /// + void CreatePlugins(); + + /// + /// Returns all the assemblies. + /// + /// An IEnumerable{Assembly}. + IEnumerable LoadAssemblies(); + + /// + /// Registers the plugin's services with the DI. + /// Note: DI is not yet instantiated yet. + /// + /// A instance. + void RegisterServices(IServiceCollection serviceCollection); + + /// + /// Saves the manifest back to disk. + /// + /// The to save. + /// The path where to save the manifest. + /// True if successful. + bool SaveManifest(PluginManifest manifest, string path); + + /// + /// Imports plugin details from a folder. + /// + /// Folder of the plugin. + void ImportPluginFrom(string folder); + + /// + /// Disable the plugin. + /// + /// The of the plug to disable. + void FailPlugin(Assembly assembly); + + /// + /// Disable the plugin. + /// + /// The of the plug to disable. + void DisablePlugin(LocalPlugin plugin); + + /// + /// Enables the plugin, disabling all other versions. + /// + /// The of the plug to disable. + void EnablePlugin(LocalPlugin plugin); + + /// + /// Attempts to find the plugin with and id of . + /// + /// Id of plugin. + /// The version of the plugin to locate. + /// A if found, otherwise null. + /// Boolean value signifying the success of the search. + bool TryGetPlugin(Guid id, Version? version, out LocalPlugin? plugin); + + /// + /// Removes the plugin. + /// + /// The plugin. + /// Outcome of the operation. + bool RemovePlugin(LocalPlugin plugin); + } +} diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index c97e75a3b2..ef9ab7a7dc 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -1,6 +1,9 @@ +#nullable enable using System; using System.Collections.Generic; using System.Globalization; +using System.Reflection; +using MediaBrowser.Model.Plugins; namespace MediaBrowser.Common.Plugins { @@ -9,36 +12,48 @@ namespace MediaBrowser.Common.Plugins /// public class LocalPlugin : IEquatable { + private readonly bool _supported; + private Version? _version; + /// /// Initializes a new instance of the class. /// - /// The plugin id. - /// The plugin name. - /// The plugin version. /// The plugin path. - public LocalPlugin(Guid id, string name, Version version, string path) + /// True if Jellyfin supports this version of the plugin. + /// The manifest record for this plugin, or null if one does not exist. + public LocalPlugin(string path, bool isSupported, PluginManifest manifest) { - Id = id; - Name = name; - Version = version; Path = path; DllFiles = new List(); + _supported = isSupported; + Manifest = manifest; } /// /// Gets the plugin id. /// - public Guid Id { get; } + public Guid Id => Manifest.Guid; /// /// Gets the plugin name. /// - public string Name { get; } + public string Name => Manifest.Name; /// /// Gets the plugin version. /// - public Version Version { get; } + public Version Version + { + get + { + if (_version == null) + { + _version = Version.Parse(Manifest.Version); + } + + return _version; + } + } /// /// Gets the plugin path. @@ -51,26 +66,24 @@ namespace MediaBrowser.Common.Plugins public List DllFiles { get; } /// - /// == operator. + /// Gets or sets the instance of this plugin. /// - /// Left item. - /// Right item. - /// Comparison result. - public static bool operator ==(LocalPlugin left, LocalPlugin right) - { - return left.Equals(right); - } + public IPlugin? Instance { get; set; } /// - /// != operator. + /// Gets a value indicating whether Jellyfin supports this version of the plugin, and it's enabled. /// - /// Left item. - /// Right item. - /// Comparison result. - public static bool operator !=(LocalPlugin left, LocalPlugin right) - { - return !left.Equals(right); - } + public bool IsEnabledAndSupported => _supported && Manifest.Status >= PluginStatus.Active; + + /// + /// Gets a value indicating whether the plugin has a manifest. + /// + public PluginManifest Manifest { get; } + + /// + /// Gets or sets a value indicating the assembly of the plugin. + /// + public Assembly? Assembly { get; set; } /// /// Compare two . @@ -80,10 +93,15 @@ namespace MediaBrowser.Common.Plugins /// Comparison result. public static int Compare(LocalPlugin a, LocalPlugin b) { + if (a == null || b == null) + { + throw new ArgumentNullException(a == null ? nameof(a) : nameof(b)); + } + var compare = string.Compare(a.Name, b.Name, true, CultureInfo.InvariantCulture); // Id is not equal but name is. - if (a.Id != b.Id && compare == 0) + if (!a.Id.Equals(b.Id) && compare == 0) { compare = a.Id.CompareTo(b.Id); } @@ -91,8 +109,20 @@ namespace MediaBrowser.Common.Plugins return compare == 0 ? a.Version.CompareTo(b.Version) : compare; } + /// + /// Returns the plugin information. + /// + /// A instance containing the information. + public PluginInfo GetPluginInfo() + { + var inst = Instance?.GetPluginInfo() ?? new PluginInfo(Manifest.Name, Version, Manifest.Description, Manifest.Guid, true); + inst.Status = Manifest.Status; + inst.HasImage = !string.IsNullOrEmpty(Manifest.ImageUrl); + return inst; + } + /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is LocalPlugin other && this.Equals(other); } @@ -104,16 +134,14 @@ namespace MediaBrowser.Common.Plugins } /// - public bool Equals(LocalPlugin other) + public bool Equals(LocalPlugin? other) { - // Do not use == or != for comparison as this class overrides the operators. - if (object.ReferenceEquals(other, null)) + if (other == null) { return false; } - return Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) - && Id.Equals(other.Id); + return Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) && Id.Equals(other.Id) && Version.Equals(other.Version); } } } diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs new file mode 100644 index 0000000000..b88275718a --- /dev/null +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -0,0 +1,85 @@ +#nullable enable +using System; +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Defines a Plugin manifest file. + /// + public class PluginManifest + { + /// + /// Gets or sets the category of the plugin. + /// + public string Category { get; set; } = string.Empty; + + /// + /// Gets or sets the changelog information. + /// + public string Changelog { get; set; } = string.Empty; + + /// + /// Gets or sets the description of the plugin. + /// + public string Description { get; set; } = string.Empty; + + /// + /// Gets or sets the Global Unique Identifier for the plugin. + /// +#pragma warning disable CA1720 // Identifier contains type name + public Guid Guid { get; set; } +#pragma warning restore CA1720 // Identifier contains type name + + /// + /// Gets or sets the Name of the plugin. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets an overview of the plugin. + /// + public string Overview { get; set; } = string.Empty; + + /// + /// Gets or sets the owner of the plugin. + /// + public string Owner { get; set; } = string.Empty; + + /// + /// Gets or sets the compatibility version for the plugin. + /// + public string TargetAbi { get; set; } = string.Empty; + + /// + /// Gets or sets the upper compatibility version for the plugin. + /// + public string MaxAbi { get; set; } = string.Empty; + + /// + /// Gets or sets the timestamp of the plugin. + /// + public DateTime Timestamp { get; set; } + + /// + /// Gets or sets the Version number of the plugin. + /// + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether this plugin should be ignored. + /// + public PluginStatus Status { get; set; } + + /// + /// Gets or sets a value indicating whether this plugin should automatically update. + /// + public bool AutoUpdate { get; set; } = true; + + /// + /// Gets or sets a value indicating whether this plugin has an image. + /// Image must be located in the local plugin folder. + /// + public string? ImageUrl { get; set; } + } +} diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 585b1ee19e..dd9e0cc3f9 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#nullable enable using System; using System.Collections.Generic; @@ -9,6 +9,9 @@ using MediaBrowser.Model.Updates; namespace MediaBrowser.Common.Updates { + /// + /// Defines the . + /// public interface IInstallationManager : IDisposable { /// @@ -21,12 +24,13 @@ namespace MediaBrowser.Common.Updates /// /// Name of the repository. /// The URL to query. + /// Filter out incompatible plugins. /// The cancellation token. /// Task{IReadOnlyList{PackageInfo}}. - Task> GetPackages(string manifestName, string manifest, CancellationToken cancellationToken = default); + Task> GetPackages(string manifestName, string manifest, bool filterIncompatible, CancellationToken cancellationToken = default); /// - /// Gets all available packages. + /// Gets all available packages that are supported by this version. /// /// The cancellation token. /// Task{IReadOnlyList{PackageInfo}}. @@ -42,9 +46,11 @@ namespace MediaBrowser.Common.Updates /// All plugins matching the requirements. IEnumerable FilterPackages( IEnumerable availablePackages, - string name = null, - Guid guid = default, - Version specificVersion = null); + string? name = null, +#pragma warning disable CA1720 // Identifier contains type name + Guid? guid = default, +#pragma warning restore CA1720 // Identifier contains type name + Version? specificVersion = null); /// /// Returns all compatible versions ordered from newest to oldest. @@ -57,13 +63,15 @@ namespace MediaBrowser.Common.Updates /// All compatible versions ordered from newest to oldest. IEnumerable GetCompatibleVersions( IEnumerable availablePackages, - string name = null, - Guid guid = default, - Version minVersion = null, - Version specificVersion = null); + string? name = null, +#pragma warning disable CA1720 // Identifier contains type name + Guid? guid = default, +#pragma warning restore CA1720 // Identifier contains type name + Version? minVersion = null, + Version? specificVersion = null); /// - /// Returns the available plugin updates. + /// Returns the available compatible plugin updates. /// /// The cancellation token. /// The available plugin updates. @@ -81,7 +89,7 @@ namespace MediaBrowser.Common.Updates /// Uninstalls a plugin. /// /// The plugin. - void UninstallPlugin(IPlugin plugin); + void UninstallPlugin(LocalPlugin plugin); /// /// Cancels the installation. diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs index 61178f631c..adf336313f 100644 --- a/MediaBrowser.Common/Updates/InstallationEventArgs.cs +++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs @@ -1,14 +1,21 @@ -#pragma warning disable CS1591 - using System; using MediaBrowser.Model.Updates; namespace MediaBrowser.Common.Updates { + /// + /// Defines the . + /// public class InstallationEventArgs : EventArgs { + /// + /// Gets or sets the . + /// public InstallationInfo InstallationInfo { get; set; } + /// + /// Gets or sets the . + /// public VersionInfo VersionInfo { get; set; } } } diff --git a/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs index 7510b62b88..a111e6d829 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs @@ -1,18 +1,19 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; namespace MediaBrowser.Controller.Events.Updates { /// /// An event that occurs when a plugin is uninstalled. /// - public class PluginUninstalledEventArgs : GenericEventArgs + public class PluginUninstalledEventArgs : GenericEventArgs { /// /// Initializes a new instance of the class. /// /// The plugin. - public PluginUninstalledEventArgs(IPlugin arg) : base(arg) + public PluginUninstalledEventArgs(PluginInfo arg) : base(arg) { } } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 2456da826f..92b2d43ce2 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -19,8 +19,6 @@ namespace MediaBrowser.Controller { event EventHandler HasUpdateAvailableChanged; - IServiceProvider ServiceProvider { get; } - bool CoreStartupHasCompleted { get; } bool CanLaunchWebBrowser { get; } @@ -122,13 +120,5 @@ namespace MediaBrowser.Controller string ExpandVirtualPath(string path); string ReverseVirtualPath(string path); - - /// - /// Gets the list of local plugins. - /// - /// Plugin base directory. - /// Cleanup old plugins. - /// Enumerable of local plugins. - IEnumerable GetLocalPlugins(string path, bool cleanup = true); } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 0dbd51bdc1..de3d3b6ff1 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -449,5 +449,15 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets the how many metadata refreshes can run concurrently. /// public int LibraryMetadataRefreshConcurrency { get; set; } + + /// + /// Gets or sets a value indicating whether older plugins should automatically be deleted from the plugin folder. + /// + public bool RemoveOldPlugins { get; set; } + + /// + /// Gets or sets a value indicating whether plugin image should be disabled. + /// + public bool DisablePluginImages { get; set; } } } diff --git a/MediaBrowser.Model/Plugins/PluginInfo.cs b/MediaBrowser.Model/Plugins/PluginInfo.cs index dd215192f9..52c99b9c3e 100644 --- a/MediaBrowser.Model/Plugins/PluginInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginInfo.cs @@ -1,4 +1,7 @@ -#nullable disable +#nullable enable + +using System; + namespace MediaBrowser.Model.Plugins { /// @@ -6,34 +9,46 @@ namespace MediaBrowser.Model.Plugins /// public class PluginInfo { + /// + /// Initializes a new instance of the class. + /// + /// The plugin name. + /// The plugin . + /// The plugin description. + /// The . + /// True if this plugin can be uninstalled. + public PluginInfo(string name, Version version, string description, Guid id, bool canUninstall) + { + Name = name; + Version = version?.ToString() ?? throw new ArgumentNullException(nameof(version)); + Description = description; + Id = id.ToString(); + CanUninstall = canUninstall; + } + /// /// Gets or sets the name. /// - /// The name. public string Name { get; set; } /// /// Gets or sets the version. /// - /// The version. public string Version { get; set; } /// /// Gets or sets the name of the configuration file. /// - /// The name of the configuration file. - public string ConfigurationFileName { get; set; } + public string? ConfigurationFileName { get; set; } /// /// Gets or sets the description. /// - /// The description. public string Description { get; set; } /// /// Gets or sets the unique id. /// - /// The unique id. public string Id { get; set; } /// @@ -42,9 +57,13 @@ namespace MediaBrowser.Model.Plugins public bool CanUninstall { get; set; } /// - /// Gets or sets the image URL. + /// Gets or sets a value indicating whether this plugin has a valid image. + /// + public bool HasImage { get; set; } + + /// + /// Gets or sets a value indicating the status of the plugin. /// - /// The image URL. - public string ImageUrl { get; set; } + public PluginStatus Status { get; set; } } } diff --git a/MediaBrowser.Model/Plugins/PluginStatus.cs b/MediaBrowser.Model/Plugins/PluginStatus.cs new file mode 100644 index 0000000000..439968ba8a --- /dev/null +++ b/MediaBrowser.Model/Plugins/PluginStatus.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable SA1602 // Enumeration items should be documented +namespace MediaBrowser.Model.Plugins +{ + /// + /// Plugin load status. + /// + public enum PluginStatus + { + RestartRequired = 1, + Active = 0, + Disabled = -1, + NotSupported = -2, + Malfunction = -3, + Superceded = -4 + } +} diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index 5e93043639..77e2d8d88a 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable enable using System; using System.Collections.Generic; @@ -9,55 +9,70 @@ namespace MediaBrowser.Model.Updates /// public class PackageInfo { + /// + /// Initializes a new instance of the class. + /// + public PackageInfo() + { + Versions = Array.Empty(); + Guid = string.Empty; + Category = string.Empty; + Name = string.Empty; + Overview = string.Empty; + Owner = string.Empty; + Description = string.Empty; + } + /// /// Gets or sets the name. /// /// The name. - public string name { get; set; } + public string Name { get; set; } /// /// Gets or sets a long description of the plugin containing features or helpful explanations. /// /// The description. - public string description { get; set; } + public string Description { get; set; } /// /// Gets or sets a short overview of what the plugin does. /// /// The overview. - public string overview { get; set; } + public string Overview { get; set; } /// /// Gets or sets the owner. /// /// The owner. - public string owner { get; set; } + public string Owner { get; set; } /// /// Gets or sets the category. /// /// The category. - public string category { get; set; } + public string Category { get; set; } /// - /// The guid of the assembly associated with this plugin. + /// Gets or sets the guid of the assembly associated with this plugin. /// This is used to identify the proper item for automatic updates. /// /// The name. - public string guid { get; set; } +#pragma warning disable CA1720 // Identifier contains type name + public string Guid { get; set; } +#pragma warning restore CA1720 // Identifier contains type name /// /// Gets or sets the versions. /// /// The versions. - public IList versions { get; set; } +#pragma warning disable CA2227 // Collection properties should be read only + public IList Versions { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only /// - /// Initializes a new instance of the class. + /// Gets or sets the image url for the package. /// - public PackageInfo() - { - versions = Array.Empty(); - } + public string? ImageUrl { get; set; } } } diff --git a/MediaBrowser.Model/Updates/VersionInfo.cs b/MediaBrowser.Model/Updates/VersionInfo.cs index 844170999a..1e07c9f26b 100644 --- a/MediaBrowser.Model/Updates/VersionInfo.cs +++ b/MediaBrowser.Model/Updates/VersionInfo.cs @@ -1,6 +1,6 @@ -#nullable disable +#nullable enable -using System; +using SysVersion = System.Version; namespace MediaBrowser.Model.Updates { @@ -9,68 +9,68 @@ namespace MediaBrowser.Model.Updates /// public class VersionInfo { - private Version _version; + private SysVersion? _version; /// /// Gets or sets the version. /// /// The version. - public string version + public string Version { - get - { - return _version == null ? string.Empty : _version.ToString(); - } + get => _version == null ? string.Empty : _version.ToString(); - set - { - _version = Version.Parse(value); - } + set => _version = SysVersion.Parse(value); } /// - /// Gets the version as a . + /// Gets the version as a . /// - public Version VersionNumber => _version; + public SysVersion VersionNumber => _version ?? new SysVersion(0, 0, 0); /// /// Gets or sets the changelog for this version. /// /// The changelog. - public string changelog { get; set; } + public string? Changelog { get; set; } /// /// Gets or sets the ABI that this version was built against. /// /// The target ABI version. - public string targetAbi { get; set; } + public string? TargetAbi { get; set; } + + /// + /// Gets or sets the maximum ABI that this version will work with. + /// + /// The target ABI version. + public string? MaxAbi { get; set; } /// /// Gets or sets the source URL. /// /// The source URL. - public string sourceUrl { get; set; } + public string? SourceUrl { get; set; } /// /// Gets or sets a checksum for the binary. /// /// The checksum. - public string checksum { get; set; } + public string? Checksum { get; set; } /// /// Gets or sets a timestamp of when the binary was built. /// /// The timestamp. - public string timestamp { get; set; } + public string? Timestamp { get; set; } /// /// Gets or sets the repository name. /// - public string repositoryName { get; set; } + public string RepositoryName { get; set; } = string.Empty; /// /// Gets or sets the repository url. /// - public string repositoryUrl { get; set; } + public string RepositoryUrl { get; set; } = string.Empty; } } diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 5a807372d7..36518377c4 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30503.244 MinimumVisualStudioVersion = 10.0.40219.1 @@ -70,7 +71,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jell EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\NetworkTesting\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From a246a77ada21466587eb7fe02cc50033ab91c2e3 Mon Sep 17 00:00:00 2001 From: Greenback Date: Mon, 14 Dec 2020 23:08:04 +0000 Subject: [PATCH 02/77] Delete plugin working. --- .../Plugins/PluginManager.cs | 51 ++++++++++++------- Jellyfin.Api/Controllers/PackageController.cs | 3 -- Jellyfin.Api/Controllers/PluginsController.cs | 32 +++++------- MediaBrowser.Model/Plugins/PluginStatus.cs | 3 +- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 8596dfcf8a..1377c80eaa 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -78,8 +78,27 @@ namespace Emby.Server.Implementations /// An IEnumerable{Assembly}. public IEnumerable LoadAssemblies() { + // Attempt to remove any deleted plugins and change any successors to be active. + for (int a = _plugins.Count - 1; a >= 0; a--) + { + var plugin = _plugins[a]; + if (plugin.Manifest.Status == PluginStatus.DeleteOnStartup && DeletePlugin(plugin)) + { + UpdateSuccessors(plugin); + } + } + + // Now load the assemblies.. foreach (var plugin in _plugins) { + CheckIfStillSuperceded(plugin); + + if (plugin.IsEnabledAndSupported == false) + { + _logger.LogInformation("Skipping disabled plugin {Version} of {Name} ", plugin.Version, plugin.Name); + continue; + } + foreach (var file in plugin.DllFiles) { try @@ -183,15 +202,13 @@ namespace Emby.Server.Implementations throw new ArgumentNullException(nameof(plugin)); } - plugin.Instance?.OnUninstalling(); - if (DeletePlugin(plugin)) { return true; } // Unable to delete, so disable. - return ChangePluginState(plugin, PluginStatus.Disabled); + return ChangePluginState(plugin, PluginStatus.DeleteOnStartup); } /// @@ -205,11 +222,18 @@ namespace Emby.Server.Implementations { if (version == null) { - // If no version is given, return the largest version number. (This is for backwards compatibility). - plugin = _plugins.Where(p => p.Id.Equals(id)).OrderByDescending(p => p.Version).FirstOrDefault(); + // If no version is given, return the current instance. + var plugins = _plugins.Where(p => p.Id.Equals(id)); + + plugin = plugins.FirstOrDefault(p => p.Instance != null); + if (plugin == null) + { + plugin = plugins.OrderByDescending(p => p.Version).FirstOrDefault(); + } } else { + // Match id and version number. plugin = _plugins.FirstOrDefault(p => p.Id.Equals(id) && p.Version.Equals(version)); } @@ -264,7 +288,6 @@ namespace Emby.Server.Implementations var predecessor = _plugins.OrderByDescending(p => p.Version) .FirstOrDefault( p => p.Id.Equals(plugin.Id) - && p.Name.Equals(plugin.Name, StringComparison.OrdinalIgnoreCase) && p.IsEnabledAndSupported && p.Version != plugin.Version); @@ -381,17 +404,6 @@ namespace Emby.Server.Implementations // Find the record for this plugin. var plugin = GetPluginByType(type); - if (plugin != null) - { - CheckIfStillSuperceded(plugin); - - if (plugin.IsEnabledAndSupported == true) - { - _logger.LogInformation("Skipping disabled plugin {Version} of {Name} ", plugin.Version, plugin.Name); - return null; - } - } - try { _logger.LogDebug("Creating instance of {Type}", type); @@ -489,6 +501,7 @@ namespace Emby.Server.Implementations { _logger.LogDebug("Deleting {Path}", plugin.Path); Directory.Delete(plugin.Path, true); + _plugins.Remove(plugin); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception e) @@ -661,8 +674,8 @@ namespace Emby.Server.Implementations continue; } - // Update the manifest so its not loaded next time. - manifest.Status = PluginStatus.Disabled; + manifest.Status = PluginStatus.DeleteOnStartup; + SaveManifest(manifest, entry.Path); } } diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index 622a0fe00d..d139159aa0 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -46,7 +46,6 @@ namespace Jellyfin.Api.Controllers /// A containing package information. [HttpGet("Packages/{name}")] [ProducesResponseType(StatusCodes.Status200OK)] - [Produces(JsonDefaults.CamelCaseMediaType)] public async Task> GetPackageInfo( [FromRoute, Required] string name, [FromQuery] Guid? assemblyGuid) @@ -73,7 +72,6 @@ namespace Jellyfin.Api.Controllers /// An containing available packages information. [HttpGet("Packages")] [ProducesResponseType(StatusCodes.Status200OK)] - [Produces(JsonDefaults.CamelCaseMediaType)] public async Task> GetPackages() { IEnumerable packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); @@ -148,7 +146,6 @@ namespace Jellyfin.Api.Controllers /// An containing the list of package repositories. [HttpGet("Repositories")] [ProducesResponseType(StatusCodes.Status200OK)] - [Produces(JsonDefaults.CamelCaseMediaType)] public ActionResult> GetRepositories() { return _serverConfigurationManager.Configuration.PluginRepositories; diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 3f366dd79f..d7a67389d1 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -14,7 +14,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Json; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; @@ -130,7 +129,7 @@ namespace Jellyfin.Api.Controllers /// Plugin enabled. /// Plugin not found. /// An on success, or a if the file could not be found. - [HttpPost("{pluginId}/Enable")] + [HttpPost("{pluginId}/{version}/Enable")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -153,7 +152,7 @@ namespace Jellyfin.Api.Controllers /// Plugin disabled. /// Plugin not found. /// An on success, or a if the file could not be found. - [HttpPost("{pluginId}/Disable")] + [HttpPost("{pluginId}/{version}/Disable")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -176,11 +175,11 @@ namespace Jellyfin.Api.Controllers /// Plugin uninstalled. /// Plugin not found. /// An on success, or a if the file could not be found. - [HttpDelete("{pluginId}")] + [HttpDelete("{pluginId}/{version}")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId, Version version) + public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { @@ -195,7 +194,6 @@ namespace Jellyfin.Api.Controllers /// Gets plugin configuration. /// /// Plugin id. - /// Plugin version. /// Plugin configuration returned. /// Plugin not found or plugin configuration not found. /// Plugin configuration. @@ -203,9 +201,9 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesFile(MediaTypeNames.Application.Json)] - public ActionResult GetPluginConfiguration([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + public ActionResult GetPluginConfiguration([FromRoute, Required] Guid pluginId) { - if (_pluginManager.TryGetPlugin(pluginId, version, out var plugin) + if (_pluginManager.TryGetPlugin(pluginId, null, out var plugin) && plugin!.Instance is IHasPluginConfiguration configPlugin) { return configPlugin.Configuration; @@ -221,7 +219,6 @@ namespace Jellyfin.Api.Controllers /// Accepts plugin configuration as JSON body. /// /// Plugin id. - /// Plugin version. /// Plugin configuration updated. /// Plugin not found or plugin does not have configuration. /// @@ -232,9 +229,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("{pluginId}/Configuration")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task UpdatePluginConfiguration([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + public async Task UpdatePluginConfiguration([FromRoute, Required] Guid pluginId) { - if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin) + if (!_pluginManager.TryGetPlugin(pluginId, null, out var plugin) || plugin?.Instance is not IHasPluginConfiguration configPlugin) { return NotFound(); @@ -258,12 +255,12 @@ namespace Jellyfin.Api.Controllers /// Plugin version. /// Plugin image returned. /// Plugin's image. - [HttpGet("{pluginId}/Image")] + [HttpGet("{pluginId}/{version}/Image")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesImageFile] [AllowAnonymous] - public ActionResult GetPluginImage([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + public ActionResult GetPluginImage([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { @@ -292,12 +289,12 @@ namespace Jellyfin.Api.Controllers /// Plugin version. /// Plugin image returned. /// Plugin's image. - [HttpGet("{pluginId}/StatusImage")] + [HttpGet("{pluginId}/{version}/StatusImage")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesImageFile] [AllowAnonymous] - public ActionResult GetPluginStatusImage([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + public ActionResult GetPluginStatusImage([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { @@ -316,7 +313,6 @@ namespace Jellyfin.Api.Controllers /// Gets a plugin's manifest. /// /// Plugin id. - /// Plugin version. /// Plugin manifest returned. /// Plugin not found. /// @@ -328,9 +324,9 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesFile(MediaTypeNames.Application.Json)] - public ActionResult GetPluginManifest([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + public ActionResult GetPluginManifest([FromRoute, Required] Guid pluginId) { - if (_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) + if (_pluginManager.TryGetPlugin(pluginId, null, out var plugin)) { return Ok(plugin!.Manifest); } diff --git a/MediaBrowser.Model/Plugins/PluginStatus.cs b/MediaBrowser.Model/Plugins/PluginStatus.cs index 439968ba8a..a953206e89 100644 --- a/MediaBrowser.Model/Plugins/PluginStatus.cs +++ b/MediaBrowser.Model/Plugins/PluginStatus.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Model.Plugins Disabled = -1, NotSupported = -2, Malfunction = -3, - Superceded = -4 + Superceded = -4, + DeleteOnStartup = -5 } } From d2d45295fc2a948f9a6945f56197097d0f2ef9d6 Mon Sep 17 00:00:00 2001 From: Greenback Date: Mon, 14 Dec 2020 23:13:29 +0000 Subject: [PATCH 03/77] Rollback change. --- Jellyfin.Api/Models/ConfigurationPageInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index 155e116a56..3c553a1b50 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -38,7 +38,7 @@ namespace Jellyfin.Api.Models EnableInMainMenu = page.EnableInMainMenu; MenuSection = page.MenuSection; MenuIcon = page.MenuIcon; - DisplayName = string.IsNullOrWhiteSpace(page.DisplayName) ? plugin?.Name ?? page.DisplayName : page.DisplayName; + DisplayName = string.IsNullOrWhiteSpace(page.DisplayName) ? plugin?.Name : page.DisplayName; // Don't use "N" because it needs to match Plugin.Id PluginId = plugin?.Id.ToString(); From 494ace7984edab77df7f9da30e411d8d3d617036 Mon Sep 17 00:00:00 2001 From: Greenback Date: Mon, 14 Dec 2020 23:39:47 +0000 Subject: [PATCH 04/77] Mark plugin failure on DI Loop. --- Emby.Server.Implementations/ApplicationHost.cs | 5 +++++ Jellyfin.Api/Controllers/PackageController.cs | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f88c6c6208..8df1ec3008 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -406,6 +406,11 @@ namespace Emby.Server.Implementations Logger.LogError("Called from: {stack}", entry.FullName); } + if (type is IPlugin) + { + _pluginManager.FailPlugin(type.Assembly); + } + throw new ExternalException("DI Loop detected."); } diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index d139159aa0..459e68da78 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -2,9 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Net.Mime; using System.Threading.Tasks; -using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using MediaBrowser.Common.Json; using MediaBrowser.Common.Updates; @@ -46,6 +44,7 @@ namespace Jellyfin.Api.Controllers /// A containing package information. [HttpGet("Packages/{name}")] [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(JsonDefaults.CamelCaseMediaType)] public async Task> GetPackageInfo( [FromRoute, Required] string name, [FromQuery] Guid? assemblyGuid) @@ -72,6 +71,7 @@ namespace Jellyfin.Api.Controllers /// An containing available packages information. [HttpGet("Packages")] [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(JsonDefaults.CamelCaseMediaType)] public async Task> GetPackages() { IEnumerable packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); @@ -146,6 +146,7 @@ namespace Jellyfin.Api.Controllers /// An containing the list of package repositories. [HttpGet("Repositories")] [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(JsonDefaults.CamelCaseMediaType)] public ActionResult> GetRepositories() { return _serverConfigurationManager.Configuration.PluginRepositories; From fbb20ebef6dee03de27e20d7e110e709ba2f20e9 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 00:42:59 +0000 Subject: [PATCH 05/77] Plugin setting migration to folders. --- .../ApplicationHost.cs | 5 +-- .../Plugins/PluginManager.cs | 11 ++++- MediaBrowser.Common/Plugins/BasePlugin.cs | 42 ++++++++++++++++++- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8df1ec3008..ddb48ff6e4 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -406,10 +406,7 @@ namespace Emby.Server.Implementations Logger.LogError("Called from: {stack}", entry.FullName); } - if (type is IPlugin) - { - _pluginManager.FailPlugin(type.Assembly); - } + _pluginManager.FailPlugin(type.Assembly); throw new ExternalException("DI Loop detected."); } diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 1377c80eaa..07b7297482 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -296,7 +296,7 @@ namespace Emby.Server.Implementations return; } - if (!ChangePluginState(predecessor, PluginStatus.Superceded)) + if (predecessor.Manifest.Status == PluginStatus.Active && !ChangePluginState(predecessor, PluginStatus.Superceded)) { _logger.LogError("Unable to disable version {Version} of {Name}", predecessor.Version, predecessor.Name); } @@ -314,7 +314,10 @@ namespace Emby.Server.Implementations throw new ArgumentNullException(nameof(assembly)); } - var plugin = _plugins.Where(p => assembly.Equals(p.Assembly)).FirstOrDefault(); + var plugin = _plugins.Where( + p => assembly.Equals(p.Assembly) + || string.Equals(assembly.Location, assembly.Location, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); if (plugin == null) { // A plugin's assembly didn't cause this issue, so ignore it. @@ -403,6 +406,10 @@ namespace Emby.Server.Implementations { // Find the record for this plugin. var plugin = GetPluginByType(type); + if (plugin?.Manifest.Status < PluginStatus.Active) + { + return null; + } try { diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index b918fc4f6d..a0d6b8f836 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -134,7 +134,26 @@ namespace MediaBrowser.Common.Plugins var assemblyName = assembly.GetName(); var assemblyFilePath = assembly.Location; - var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + // Find out the plugin folder. + bool inPluginFolder = assemblyFilePath.StartsWith(ApplicationPaths.PluginsPath, StringComparison.OrdinalIgnoreCase); + string path, dataFolderPath; + + var configurationFileName = Path.ChangeExtension(Path.GetFileName(assemblyFilePath), ".xml"); + if (inPluginFolder) + { + // Normal plugin. + path = assemblyFilePath.Substring(ApplicationPaths.PluginsPath.Length).Split('\\', StringSplitOptions.RemoveEmptyEntries)[0]; + dataFolderPath = Path.Combine( + Path.Combine(ApplicationPaths.PluginsPath, path), + configurationFileName); + ConfigurationFilePath = dataFolderPath; + } + else + { + // Provider + dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + ConfigurationFilePath = Path.Combine(ApplicationPaths.PluginConfigurationsPath, configurationFileName); + } assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); @@ -146,6 +165,25 @@ namespace MediaBrowser.Common.Plugins assemblyPlugin.SetId(assemblyId); } + + // TODO : Simplify this, once migration support is ceased. + if (inPluginFolder) + { + var oldConfigFilePath = Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName); + + if (!File.Exists(ConfigurationFilePath) && File.Exists(oldConfigFilePath)) + { + // Migrate settings, as different plugin versions may have different settings. + try + { + File.Copy(oldConfigFilePath, ConfigurationFilePath); + } + catch + { + // Unable to migrate settings. + } + } + } } if (this is IHasPluginConfiguration hasPluginConfiguration) @@ -219,7 +257,7 @@ namespace MediaBrowser.Common.Plugins /// Gets the full path to the configuration file. /// /// The configuration file path. - public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName); + public string ConfigurationFilePath { get; } /// /// Gets the plugin configuration. From 356d92cd71c698f91d76d6437166c90ee656a900 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 00:49:14 +0000 Subject: [PATCH 06/77] Fixed repository listing --- Jellyfin.Api/Controllers/PackageController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index 459e68da78..906188026b 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -146,7 +146,6 @@ namespace Jellyfin.Api.Controllers /// An containing the list of package repositories. [HttpGet("Repositories")] [ProducesResponseType(StatusCodes.Status200OK)] - [Produces(JsonDefaults.CamelCaseMediaType)] public ActionResult> GetRepositories() { return _serverConfigurationManager.Configuration.PluginRepositories; From 0d4aa6bad61a8d7ef74b88a161913c5a64663937 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 01:13:11 +0000 Subject: [PATCH 07/77] Enable local file repositories --- .../Updates/InstallationManager.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index b0a1750bdd..325955e207 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Json; using System.Security.Cryptography; +using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -105,8 +106,20 @@ namespace Emby.Server.Implementations.Updates { try { - var packages = await _httpClientFactory.CreateClient(NamedClient.Default) - .GetFromJsonAsync>(new Uri(manifest), _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + List? packages; + var uri = new Uri(manifest); + if (uri.Scheme.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + packages = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetFromJsonAsync>(uri, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + } + else + { + // Local Packages + var data = File.ReadAllText(manifest, Encoding.UTF8); + packages = JsonSerializer.Deserialize>(data, _jsonSerializerOptions); + } + if (packages == null) { return Array.Empty(); @@ -150,6 +163,11 @@ namespace Emby.Server.Implementations.Updates return packages; } + catch (IOException ex) + { + _logger.LogError(ex, "Cannot locate the plugin manifest {Manifest}", manifest); + return Array.Empty(); + } catch (JsonException ex) { _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest); From cddc87e2af241b6b2149a5c9e1a2b2714e482613 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 01:23:52 +0000 Subject: [PATCH 08/77] Fixed gitmerge. --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index e58f02d410..75a9ca0801 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -231,7 +231,7 @@ namespace Emby.Server.Implementations.Updates } // Don't add a package that doesn't have any compatible versions. - if (package.versions.Count == 0) + if (package.Versions.Count == 0) { continue; } From 2bb12793b29dc078b3f1597afc4ab8e366f098a7 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 01:31:56 +0000 Subject: [PATCH 09/77] Add files via upload --- Emby.Server.Implementations/Plugins/Disabled.png | Bin 0 -> 1790 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Emby.Server.Implementations/Plugins/Disabled.png diff --git a/Emby.Server.Implementations/Plugins/Disabled.png b/Emby.Server.Implementations/Plugins/Disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb8ffefc2d008695cb1f8fbcce47ee84ae5c277 GIT binary patch literal 1790 zcmVwb+pB^%Z{LF1Y=(%4aNu&-2;%9(!-o&Y z;&&4Z1wdF>SfEZ!+6MpJxCIbfc~H}6S&ViMfAd5ao<+`S7b%I`pWdPXP?+fhdr3IH2b zl7IDTdvr`3^L|}D=yX~Npt8Ia(lauAalQxDXuIh0<;%j%%uJ1=065pKT~lFl2}pv? z612Hs`9(%;ggd2W)RdHz2rphdg8{V$7>wC>;Pd$~K0XdAVBO}lQ6_AmUK>+k$ zxpL(fHk+M-_$8RY_cR*K{*xz9wvr_T01wq|5)5H9Lipq)(VjhfcK@(*=cRz4AZUHx z=1>?&2-bzKg@(tEXdV%f`1rswzvbZU>wvvBqR*utbk$dAks160S^ca3<8a20JQi5ve_K^$nbbPde8(uHd>6W(P)_E zVjqbQDB4<~v$F#@xXw>11wX%Kbl<){CA@n160qElWBLC!jf%%6#(>M_(oL}QMKHtk zRdaE10Rb{3d5ngKhrpmWfKVg?i9`Y;NK%7CgVS8>yVRFb@xg-!w_zeO&YnGc3Oynt z^9F-KT~=0hx}>B;j>B#Xfc$)ATtYH*_jJv2Esx6s4wns28XBOdr@L06P&^{`6)RS3 z^!Ap-kPsn7(LTWnK;kWdYIz0e7Xr~ViYfw2sgsvZ2NrT7IywaaJ)lx4XT?<@5WTEqP2F3#X-hQLbw611UW_zeQ%M03A|m~wGf8T&l)D}* zd_(#KN<`v(1VCCY@(#=t5xTlsUP)MO4Gz0ts{JAxF1eJKS0js!@irM5m=F4yYaDwE z@~H=sU}$h~+9Z?7LYErh(Zh$@Lx&C#carwYWV6;ICnu+Ni!3^r>*Pe@OD7h)xk1g> z04SIQKp1PL@G$}J)!v1)ef#Fx%pJjh-Ykoz151+vG-(NdbVmW;&xbMre?CY_O(PJ@ z2Nsggo7bc)zWdHyG07*qoM6N<$f?{PtRsaA1 literal 0 HcmV?d00001 From 3cff64ee320b94ec915ba96d52d946701c1a7ff1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 01:32:43 +0000 Subject: [PATCH 10/77] Delete disabled.png Should have a capital letter --- Emby.Server.Implementations/Plugins/disabled.png | Bin 1790 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Emby.Server.Implementations/Plugins/disabled.png diff --git a/Emby.Server.Implementations/Plugins/disabled.png b/Emby.Server.Implementations/Plugins/disabled.png deleted file mode 100644 index eeb8ffefc2d008695cb1f8fbcce47ee84ae5c277..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1790 zcmVwb+pB^%Z{LF1Y=(%4aNu&-2;%9(!-o&Y z;&&4Z1wdF>SfEZ!+6MpJxCIbfc~H}6S&ViMfAd5ao<+`S7b%I`pWdPXP?+fhdr3IH2b zl7IDTdvr`3^L|}D=yX~Npt8Ia(lauAalQxDXuIh0<;%j%%uJ1=065pKT~lFl2}pv? z612Hs`9(%;ggd2W)RdHz2rphdg8{V$7>wC>;Pd$~K0XdAVBO}lQ6_AmUK>+k$ zxpL(fHk+M-_$8RY_cR*K{*xz9wvr_T01wq|5)5H9Lipq)(VjhfcK@(*=cRz4AZUHx z=1>?&2-bzKg@(tEXdV%f`1rswzvbZU>wvvBqR*utbk$dAks160S^ca3<8a20JQi5ve_K^$nbbPde8(uHd>6W(P)_E zVjqbQDB4<~v$F#@xXw>11wX%Kbl<){CA@n160qElWBLC!jf%%6#(>M_(oL}QMKHtk zRdaE10Rb{3d5ngKhrpmWfKVg?i9`Y;NK%7CgVS8>yVRFb@xg-!w_zeO&YnGc3Oynt z^9F-KT~=0hx}>B;j>B#Xfc$)ATtYH*_jJv2Esx6s4wns28XBOdr@L06P&^{`6)RS3 z^!Ap-kPsn7(LTWnK;kWdYIz0e7Xr~ViYfw2sgsvZ2NrT7IywaaJ)lx4XT?<@5WTEqP2F3#X-hQLbw611UW_zeQ%M03A|m~wGf8T&l)D}* zd_(#KN<`v(1VCCY@(#=t5xTlsUP)MO4Gz0ts{JAxF1eJKS0js!@irM5m=F4yYaDwE z@~H=sU}$h~+9Z?7LYErh(Zh$@Lx&C#carwYWV6;ICnu+Ni!3^r>*Pe@OD7h)xk1g> z04SIQKp1PL@G$}J)!v1)ef#Fx%pJjh-Ykoz151+vG-(NdbVmW;&xbMre?CY_O(PJ@ z2Nsggo7bc)zWdHyG07*qoM6N<$f?{PtRsaA1 From 1c6529c9eb195e3cb8c439df8805652090c49a0f Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 07:54:49 +0000 Subject: [PATCH 11/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index d7a67389d1..3759f9f1c4 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult EnablePlugin([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + public ActionResult EnablePlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { From 33385c1b8cbe46b58f19716c85a3da9ec66cd282 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 07:55:14 +0000 Subject: [PATCH 12/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 3759f9f1c4..afb5dc5ff8 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -156,7 +156,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DisablePlugin([FromRoute, Required] Guid pluginId, [FromRoute] Version? version) + public ActionResult DisablePlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { From 41466c430de6ad8b3aa2599af9e6e41f1f63c785 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 09:17:06 +0000 Subject: [PATCH 13/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index afb5dc5ff8..36e37b7ad0 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -113,7 +113,6 @@ namespace Jellyfin.Api.Controllers /// List of currently installed plugins. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesFile(MediaTypeNames.Application.Json)] public ActionResult> GetPlugins() { return Ok(_pluginManager.Plugins From dddcfa6dbbca04ed69597ec335007612e2e2b8e8 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 09:29:51 +0000 Subject: [PATCH 14/77] Suggested changes. --- .../ApplicationHost.cs | 3 +- .../Emby.Server.Implementations.csproj | 7 -- .../Plugins/Active.png | Bin 1422 -> 0 bytes .../Plugins/Disabled.png | Bin 1790 -> 0 bytes .../Plugins/Malfunction.png | Bin 2091 -> 0 bytes .../Plugins/NotSupported.png | Bin 2046 -> 0 bytes .../Plugins/PluginManager.cs | 3 - .../Plugins/RestartRequired.png | Bin 1996 -> 0 bytes .../Plugins/Superceded.png | Bin 2136 -> 0 bytes Emby.Server.Implementations/Plugins/blank.png | Bin 120 -> 0 bytes Jellyfin.Api/Controllers/PluginsController.cs | 60 +++++++++--------- 11 files changed, 32 insertions(+), 41 deletions(-) delete mode 100644 Emby.Server.Implementations/Plugins/Active.png delete mode 100644 Emby.Server.Implementations/Plugins/Disabled.png delete mode 100644 Emby.Server.Implementations/Plugins/Malfunction.png delete mode 100644 Emby.Server.Implementations/Plugins/NotSupported.png delete mode 100644 Emby.Server.Implementations/Plugins/RestartRequired.png delete mode 100644 Emby.Server.Implementations/Plugins/Superceded.png delete mode 100644 Emby.Server.Implementations/Plugins/blank.png diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 404e28bdcf..17cccdaf92 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -393,8 +393,7 @@ namespace Emby.Server.Implementations if (_creatingInstances.IndexOf(type) != -1) { - Logger.LogError("DI Loop detected."); - Logger.LogError("Attempted creation of {Type}", type.FullName); + Logger.LogError("DI Loop detected in the attempted creation of {Type}", type.FullName); foreach (var entry in _creatingInstances) { Logger.LogError("Called from: {stack}", entry.FullName); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 7e0be78993..0c94f937ce 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -73,12 +73,5 @@ - - - - - - - diff --git a/Emby.Server.Implementations/Plugins/Active.png b/Emby.Server.Implementations/Plugins/Active.png deleted file mode 100644 index 3722ee5200f60eb51419a182b8e094c42c6291ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1422 zcmV;91#$X`P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1tUpBK~z{ry_bD# z6jczwXWrYr-Mzc^+Hy3sRX(I7El3YRB@F=!NDwJSK?5z3hyERUk$Awn+W1lWvaa3p$FjG^TLRcsR?^q0{Y0h8v#%l0YbuKMX0XS(^f?3h z`>dU7o81n&{NL+swibZtVsQySzLIfab@fx#wze~U%0PZ)J`7BDe(F)B<(F;*#jZ(2 z&?-6!e?Zw>cR%MAkqnGGISzDLhts~(5oLFnb?`;>Iuk!iQlzyP{Q(g57J-Rx)8w_1 zg5xWmFK>!!yM|0SHvvYskC;2UIjf}b&?}0qzb`qZIgsk~EcI%RHJ6)1(PvJh!Wa)} z8ePyG=N2UU#^CvBZe=GjzDkV9gA0$&d(6HTm-O}N@bnWS=cKwEo37jpVzl6*sW(gx zno3u>Cnp}a%^!%k4VGNJ8FCP-Y7r))2~~@Z&Yv>vz%<*WBh#l&dL}JV-Co}klEqlP zvvbZtlE{9dvrSuZt8M*Yh{5<9VxeoXT~Ne9gu{WM3EmHu94Vx>U4%D}6=tL-s1^RU zup9Bt&c!{V1hCPqNsD()FK4!Sw;-mo=Awvvu}smt-M=U@%V7hRv0~|oq5+oS(%*_y zuS+QlhK&qUR9ei|65Rxrph<84nfu?B$f`Xc(gpAQ@iM?I=jS4iDocqIW_cc8TYN6GrFKrxKT4}3#OovJ6 z@(nvLOQcv-HIG$ySHd{~#X)xY2l{*M+@jN2u=><%$SK{$8J}mXI>E)LZ8u1hsPV&Bnw;s#(nm_%>zdlAHrdv^~YwYxP|ye_BIj2ici_yGuty*q_N zTp>$X9~(HpB+wN2s`;vU#y0CST?Sy$Iz0Wl=!%t*^o!k7BGo%2zW$R*+iZ)z(_z-% zO>S;3xKFxK)1C@Cqy%*@PWm&-Mj zHM+*I7D-i7%q;(RDL<)-<`&4=+1cSyqeg|Rs=9pL>lSezK`oN*fFh7ibEKzqxj<1-5eykJ1WnUKkqI;m11O~s4u`oVb#-;TtgOto c?kxcS0~w3NKFYLwmjD0&07*qoM6N<$g7!P0tN;K2 diff --git a/Emby.Server.Implementations/Plugins/Disabled.png b/Emby.Server.Implementations/Plugins/Disabled.png deleted file mode 100644 index eeb8ffefc2d008695cb1f8fbcce47ee84ae5c277..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1790 zcmVwb+pB^%Z{LF1Y=(%4aNu&-2;%9(!-o&Y z;&&4Z1wdF>SfEZ!+6MpJxCIbfc~H}6S&ViMfAd5ao<+`S7b%I`pWdPXP?+fhdr3IH2b zl7IDTdvr`3^L|}D=yX~Npt8Ia(lauAalQxDXuIh0<;%j%%uJ1=065pKT~lFl2}pv? z612Hs`9(%;ggd2W)RdHz2rphdg8{V$7>wC>;Pd$~K0XdAVBO}lQ6_AmUK>+k$ zxpL(fHk+M-_$8RY_cR*K{*xz9wvr_T01wq|5)5H9Lipq)(VjhfcK@(*=cRz4AZUHx z=1>?&2-bzKg@(tEXdV%f`1rswzvbZU>wvvBqR*utbk$dAks160S^ca3<8a20JQi5ve_K^$nbbPde8(uHd>6W(P)_E zVjqbQDB4<~v$F#@xXw>11wX%Kbl<){CA@n160qElWBLC!jf%%6#(>M_(oL}QMKHtk zRdaE10Rb{3d5ngKhrpmWfKVg?i9`Y;NK%7CgVS8>yVRFb@xg-!w_zeO&YnGc3Oynt z^9F-KT~=0hx}>B;j>B#Xfc$)ATtYH*_jJv2Esx6s4wns28XBOdr@L06P&^{`6)RS3 z^!Ap-kPsn7(LTWnK;kWdYIz0e7Xr~ViYfw2sgsvZ2NrT7IywaaJ)lx4XT?<@5WTEqP2F3#X-hQLbw611UW_zeQ%M03A|m~wGf8T&l)D}* zd_(#KN<`v(1VCCY@(#=t5xTlsUP)MO4Gz0ts{JAxF1eJKS0js!@irM5m=F4yYaDwE z@~H=sU}$h~+9Z?7LYErh(Zh$@Lx&C#carwYWV6;ICnu+Ni!3^r>*Pe@OD7h)xk1g> z04SIQKp1PL@G$}J)!v1)ef#Fx%pJjh-Ykoz151+vG-(NdbVmW;&xbMre?CY_O(PJ@ z2Nsggo7bc)zWdHyG07*qoM6N<$f?{PtRsaA1 diff --git a/Emby.Server.Implementations/Plugins/Malfunction.png b/Emby.Server.Implementations/Plugins/Malfunction.png deleted file mode 100644 index d4726150eb52857ff3ad25462517be0de9187b47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2091 zcmV+`2-Nq9P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2f#^0K~z{rwO46u zRM!>$=FPmB@opS@yx|6?nP3~-;DUh~3sV%OB83v9CO_JwL83qCB30@FiNQ75N!q5$ zB9(+BRcX`~l|m4f6E(qLOaLb~v0~!}jPY(}jF+)J#=}cWlT%=EV=c``q~|&WFNkUk@oCKDlOF(@PsQ z&*cv_ZS)ekh2syjrk6KrhkjO+Pn|n);+CXDp0^_NU9rl6yoYze;y(k_t=HLNyo%h7 zPr#mTeTO<~Sao3(&43sMn$40^0Cc`d?Kt`y3plD^j!#D@7)qgzHLSWYie>=4V}w07 zxlJ}-IW~|@3HpP{H&STQH5W+H3X}@sexlu^78e9Fd;ck zUHSQo8#K+`qiI#PFuS|8&nk;G&MzY~Ckv-&5twvM_;2_<6QRIpAJdRYk?iZ!CrP7Z zZ+JLEXZMvZiVUNYl#qJyg|Yzz{mG%wSEH#>7IRlNmOZ`)IR67m_(}0UaQWz481HX` zL7DA?7)Ru$*x(y?p{Tsh!q;hQ=OZy7`Qo!BBSpXa3lNY6WI+)}NJJXn+g>)RE0{djZkfsZ>oBa`!zd~R!adaZNiZ{A?+HdA6IPkA-9 zI^PXkRaj&?82ud2iI{AzF(ai_X#CSJu&T0Na^{p7eSRLAiJvTJWW$0SIm~t)11$E=~_{rc_6fuCx2BvDsV?e=Efc=+1#slnwVhra@ z^xP=Sve%ho6I;YwRwGX^LNf;3mUIIYBOk#@I6E}7RsR?5XD-4uzJ$h<6H$z!bRC5o zE8I9LPZ@A$q#IzMN6iQSh?@7SPgJ~J`}6mX)$MPtI_&JK`3U&t4tmAV3odjcqA`tt z;N#9nv;)T*ojZ@z_xH8*jP>%}O;+~-)gM0@dM7C-$DW+B3K;%0Y{WD$=OdY;5d^6g znvTBl!oJeyObUKtfR+{13}>8f|4+C4q5dl8q`vgSgy_+~X4BUji|mwAzSX{M`<9~D z?VHPhu76QO!h1UdxM_%3%G-_0hyTVt(aY=^khZ)KnJbH-CMILF{R+BHokaie&C*RT zT{tHj$%cdE0rFVneUdz+7z|v2BLh)A4ZtXDVwLp$6H6c2j8(gLW96=0_(oYFRPR}! z=UpUag|Kw%R@mZAr@69443Jn!-OO!+L91$*4A(ZN1zcYN++3!PlxkoI!7GPw{+T`G zy$H4+qg&Ux1Hm~^1IywJXqv_kOl>aA+~`j+EW(lg;Og}u=u1KJ(O4wC=7-vuiP1i0R<=tkhPU5AY$$rI zQi3?%Y;fPEb+*jRlh3QYUg(*!Sc=`Ve8+#1wdFCc9r^E|6MGUQO68<${#S z3R4jg3@~G@CmmScQo?$o=cO^QH!MTz=^-|^hcIzN#h1rFLiHCncJs@CK+M+oOsVtR zzy2|AKE$B3QpuHX7?|j0_(zy2-iEAp47Ogw#NaRj9v_l&GLV{o50_wW$5~NQqO7%! zPaH@)a6jU^E%?#8Nq8BQ|C}&kU`GfYv1j2|ERMg{^;W)ny89&e?&mZe)M3c-8E)V} zhkb-e)(rUk>A3XXQFJ)nr*f&Ljbb@5|=29T>S?D^}fVwY--uTtu zTMkh;9-hOIN7iJmsIaI93wL~vs!p`kUdOjawgI0ratT&=i=?hI0}YT`l_OOZ&m{tERp~LX#HjpQKVfD!D?v^uF;*?;zHkM3 zUByriGeGnlZLE&F1E{<|8_(wCaAO+OMVSPAqz88I8k~Bnn7v(KOH!n!&Ga*lcgeYI z3L-Tx9kmtf{!CSxG)=~uigo)^^V8wJ?n2W42#vdGUk>dOWL>r5=|B%E+`P;V(3yNX zC}qmRTU0%Y#02veYQ;z%Yz@zswJRYlJ~VTJO_LlZ3hm_i)?Zv+%NXnF5Q=MfVMCj0 zR5HEZN&Y3#LHS-k-$=YKMn*7Bm=Lstpcy$YayqQ=qONT#~LO z2NvNyT?K3yV0@8Ao>fhEOoFEl*^u!8fBGObcsr6)Qc7p)wjtliPYIL0AmTch_&>mp Vv#07tKs^8e002ovPDHLkV1ly-^R)l~ diff --git a/Emby.Server.Implementations/Plugins/NotSupported.png b/Emby.Server.Implementations/Plugins/NotSupported.png deleted file mode 100644 index a13c1f7c1c03605a5d7e0afdc6f4a81621330ab2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2046 zcmVqliy>cVqq8^Lf84jXzRbXHoxpjmsPn;0Hn?PQ*K6WF7k0Q)&K*4=l2RP#>Du~ z&{=*$nX*rrLCU%10^qLNpR9}9`2}z}miUm-{=~TJI_SOr4E1M4-KiywiL`EFxd8a6 z_NQp#w*M1QZ=!SFht=gCNieu?L-VPk8)+qtNu<;*7XYt{T?uDmpV^e<8$_q?KEMNr zF2i7BJ({X3KgoMv{TeC9mkWT4n@ke=U14fV%#M>lRXnfFXhF;2myuB197%8eBs0qe zKui+QRFu#k`}<=e@wGny`Vj)4BmlqW0B~t>o5XDUQSCmtyL?~my+;KgQ6E%{!EAK1 zSQe%fJ7NYV;?q21^vG zc>u4z2Q>VOG{hR?AWS(3VCb1)Yyfd5UBsppz$}n-3aWPt2tb3M0KgstDa61b3a5*J zYz?`Bf_k!>vRg@qG|r#g?jQgb0ucWZ0oX|Zl)j`KvkO37nyQK2@g_8lHJBLehe)~t zo@>^@EhGr&{SYu{ZCJ4fKo7*ePXKm5I)E#0DKrie2|D_;|2eN7tB43(`FK`@`|lo) zj`v-il?7ZsLWV6iv9SaIE(W6i3iXk{Ail8vkpOt?+ZdjAwsB;*V`$AMRAQ zK_qeD+^Go_(mHp0W8{So&Z{3v)xW^Pi9TG9*V144qpX zfRHSZGU)kOi`(Bbc;dOI;TatTu}o&QYMPmWuB{CN6%|;&{PA{o!izWVsC#)6VoAH z6N%PiM-lLB7{-}?#O$p0SPFpCsX%Oz zfT8>}Ov3}j3W5ExOmhKjHbETt1f)5eDL#(ES?-AVgLS@~3CF94G7M%jJ9dB8xHYS^ z{+pw}jgmeYDBJ7n@AXE+CaVA1N;05d=zcCMoJD9Y9e`(8E?h>tVC5FMaMf=aW74GT->AH~TlQTU?8_})m97r35K-<}eX{`<>{Ty5$^MhApFzLr| zG4V7Mn|}$tsur&2Tb4?D+~2UC;vo&nuP!39zSCDI#d1yjpYo_`*9x@R(C6rmIa2_B zI8%v!%~VzU@Khr=6BW2JIViwI5xp%Nmoh&=+SAFH8|;P4+1e!mOm39JF=!2xN)9RB}ysRiDIQm6WXL5xEja_}ntZlaiKv%Jj0*JLKw+z>SMo?Y9fc3?7(WDv1eEY>U z8-B`z>6aa@nfwE1W$^ZKgN6LhT7zyu2oMGaBFYZ!_(skkm2eEVgYHwmwrB6(G4T=3eS5;c;Os6`Zu=xgl#gn9)Ne$DcJ@IDV;_ zVqm^!hW`2t=5Fa9#54EruF2eX;kNF?r-Ng)LM(B=a?IAIlq85NNcicymckm;m0-{& z3#ed#Ys|0vjCj4!TpMw{*g&*HRnzP>t(v+FbEl76yCY-RtVW(Z*bId?fIPAuAOHXW diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 07b7297482..cf25ccf485 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -1,7 +1,6 @@ #nullable enable using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -23,8 +22,6 @@ namespace Emby.Server.Implementations /// public class PluginManager : IPluginManager { - private const int OffsetFromTopRightCorner = 38; - private readonly string _pluginsPath; private readonly Version _appVersion; private readonly JsonSerializerOptions _jsonOptions; diff --git a/Emby.Server.Implementations/Plugins/RestartRequired.png b/Emby.Server.Implementations/Plugins/RestartRequired.png deleted file mode 100644 index 65fd102a2ca7ffb99c7843fa46b1c5274ec0e76b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1996 zcmV;-2Q&DIP)cX7XQr`GXw;Kd?{+jT}C0~W0?5L5s0=A&*Dm@R@;$vPaQpbjN?|W8G{w&OtDgdf)pX7t*sk0&8fN(`ICCiT>e3u-G%34g3V_$~&5=ixPpnS#T|lR=-_8LD z+mBZ5Y1Eb%|7+EIhBrvrJyQVOJX|CTPo*!>g{OW2l&Lsvf)2X8=MbqmvzTU0BzI>D zfS6=XZDwSXa@{_m_|ke{pq&6z6Mz?40Bl+Ok7U?>Q)QM~uFk5wGhF}@!;bh43|l;` z)`=y}kj{CQKZ4h61*-qRZHfF6^+yk&x4i)Zq0s(o+h1o-9}M<(Lz(s;DI8P?3&6vW z^XMJr-}VDTtO4QJz_2QY<_-BkXF0d#wh&mnn|?CmV#sh+LUFtJaR&Akom=ohadWdH z06scRj8XN*`xLt*(m0U_?UuoT5{Nlrb<7Zn1rtb+b12r#@hN31A@k=5e4E+`e>;SPRoy4XMz&#-P5cK&Q2|$e>55Q3bVkrVUDV<|A2*4%+Q0+^~4u=2?xl4@7)C?H46)?3nK`46w zvjT$Qp;!Pk9stapc2RKzU;t3QM*y}=AHdnSLtWIS{ss0Ax%XfM?dqz*XO9+i#q2GhL^7ABlv-t9VP~C#vLU z6&`ZB#;)3FTm=tcaW-`CW)V-+umEmrHA{0vLMBDPFmr|(L4~o;^2+i&?NxB~1sRDu zZ<%E@vS6!4&$JbaXVxxP?o%Yi11$ygg~R~>N=z%2ec+q8a^!Q|yLl5*krTW_LNF^l z9B_4msj(i-r%KS?byvM?SIy~>)E08P$JCW9J$d2NDa*qP6-jEK?dbFXFhq(nN`Akm zD4~52zGoRiy}WTSZUs@&O*&Q#%)M^7@b%Y2s*FmvsjNwIS1n3h{Zz;aMbZ+W{m03# zKrk{F@Mqx3h$ShB6QT;g#}mJyR^pZrhadkvS78C`VaVSL)wU|nsQ^ez--;;+ijPY6 z3wEQMH1L&Rmp&{Fv%)5)U=mYeF`^3!@JY&B_^j$}cnta=Jt_v6zY=Hj4AyP@%)`S>N6$UW+Qi(`KkfZfh%D6KCCzuX*Vdrb#Q&Ye^xO=LJ3LLq66DmDIsyhf=@`S6_?=PBT7r(OcfIeGojClPedTPn2n8Ix(t{^+ju{0>QOkBV=XU`_gu){t9FEXIljD{RvyGB08yv ztyI##{2~XWr3EPY+2;H;`iv3`-8_J$6oJPn>5OAr2X>X%?3exJ2;WjR*~8*}Is11V zi`EfSO78LmL|SKWG`>@UfNdvN#O*}Ux;4Pn20Hl<4gI485fbmC3W9pf5Fr`;jArj%p>OGvo?OEbi8Y_+Qb zA`Gx?xi`4>eN5tm`){e6w=3oo_!fy}e3i0KoC_N`Og+qe1&kPA_dUQ@!(?NS|5n1! e`^P(H$NvG|Dd5z6`yu`S0000uUe5R3_wKvDci;W} z-U|=?Km4qo543)&6h0O6<}- zBffx*>o77Ip?gq`lApKXdPURNk5jIcn2ZAiKy*1iuyWk&*uggXbHLrb=2)KV(emhJ z6lLtm3yjMPA!Fx20gx!;0~&o+{s^3vQ9kS_K4sj|8y(J{Lv<*&bV`C^5{+vaC;)?% z@quceCHn!@ELvwXOk3{c51r(1xO_17&eQ~jKN%Yb3V`!3U-%uBy*Vq?#hq4Py3q_^ zLJF!B7m=Tnxi2zVwStU~1`2?K*iJY)YweVRiA#R~vgKxLNC67Ay$8SbmtUu^3TkZw z1wcS;Xnw3;g>2z2zMyy!&{0bO@(93(3;?6WeQMp7PPuVGlAyTUrWXYuRBZ~bL$_XR zF!ZLhk!-eu+~EC}H9+25W*5K1C{6zcn%djo@%fgcO~1BIgVCvZ1X;{^8}!2v20*Mz znA{{=l>&6J3HVPYhMA1?eR~H`pJVQG7!AC>m0pcc<L2s;=ZZ zt*>p((iyuuG4adkzXN0LgA3LNsO2kmQ|`jaVoi;2fe&U-Jb}3}o3V?U9=$}61vlz| zpbV(Ce+GGM{t&XNS(b}8<>>6arO>psqOI^6d^bj#K=c4u{%_#Y2Q)v~c~)F-D-I03 z^LQ43*h-%vekK6zK)^u)u#5mGT*=yW;Ls6WOM5eBScgK>u0?BG2Sk;{n3Oc%h64~t z8Q4PQ^c!F|infA^T2o5w&Z9-@dsg1>LjVi}z&DiuEN1|Qr5!#|9VqjJSmJ=R@Ao5& z|0bH-+u(4&1c6^Kaxwu>ZJ45#EnNq-;v8D4D!>=Lgh8XcA(pxWW#0h0dW)>|2cQFx zB}2Jw71ebM?)dG-RR3`(FS~`3(o&SxRAJ5o5BTo=B!C=lnE+hb9IUny3Q<^IfAVKl z-7j8l_QONHB=d()kh_cs4W(g8G_k>=Hu?a-YHR#foQqwIX^YoGey&J zTy%g6O3qGHM z$5Zf=rU6O$qYxc8j?PREq5w!>0hXr+aU~ z@4=18PQ6EIf9Dy&rHEcFp}dk>cXylhWAvv`?ns93R~MZ60$_9Y%YY+Z!IL6fy&ZrC z6~_dN=vG|ty;V;IKVjweEJaZkeyP_1-IC9>Px=tfn|H5 z01_T(DD{tS(i~J`PUE>PI89q{-i=KuQEi;oD4h|6n!M|1t!sopWCMxEs~8a+NM%_F zXlVk0EuAtXY}T)s({9ipd&?;b&ZuS)oKPk!J@DadIn8&C%0_@8KHhMV)5$HehW1V+ z?);jIlLfUa)*UN3VA26)e305_`5ZcG>1HLMbQ?(PJL!_6tpfCPs@l4dvlR8k<AYE*>?W;{2gJ%QVEV5j0WM5Qgnl}-EM^HuH)k&w^(ahW{E&Cw=^^>=m~8~2^B zS(AFU@_+PM%SmyNS{@xq$3`w!Sr)o)EkS43P!t`_MA?JZ%$$;joDY2L6DBXDv=3Qe z5u6H6;eqH)B;}5ipiDdp-`HGF>S$+_=BUo9M#}_QNH`uUpA62JDISB{KNa9~ zPT9`rgN26|O?M4i_0NB7_n5YXZb+Y#$ec1!AxS}0+cK64j$}maLq(R(0Jy+>U$kXK z&2dK0=?y!MjTQjtC~l-HzR^N%dF%q}MU*)S-}SlU*<@$GU3~kE1>0=9D(F8+@o@;M zGAZp+L~Wl;YojeR5Y;4}1R&!vfVw`Ih8_S>VL)>Qhlyuiy_FEUb_ZNLctPVIe_`yd z&%DCs%neVKjPOO*gA!cN%g3dw5ApYv!4QgxCAxOApQAgJJJ0+P08=Rgf1(wrpZt%* zdhG_|MoJFGug)G3yqw`R^WnuP6|IcsQBU#m(6KBiTP5A3%8=jTcyn2(*E0lpO z0&xDt0H_kCsb#bZD$(8mgnXzoZ{gDArn_I?a2xrWb-A9`a(hS0nfs@XF7=%oICbR0 zVKcpYd`-$?g)50U@L6~61v9WQxQuTw+8YeLZ#hhFh`u;pKL)o-u5Lb8 zO(7Slf2cukAd&0w<&)9C=V@jpD#qMz>bTz@05LUpCP;YxS<7bc;3}Fm4a`UOtJIvi z_ov6RYdJgf`~1_wFr%o3R|8Ox*VDx@L?S#n0SJIxRtCl{ - /// Uninstalls a plugin. + /// Uninstalls a plugin by version. /// /// Plugin id. /// Plugin version. @@ -178,7 +178,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) + public ActionResult UninstallPluginByVersion([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) { @@ -189,6 +189,35 @@ namespace Jellyfin.Api.Controllers return NoContent(); } + /// + /// Uninstalls a plugin. + /// + /// Plugin id. + /// Plugin uninstalled. + /// Plugin not found. + /// An on success, or a if the file could not be found. + [HttpDelete("{pluginId}")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Obsolete("Please use the UninstallByVersion API.")] + public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId) + { + // If no version is given, return the current instance. + var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)); + + // Select the un-instanced one first. + var plugin = plugins.FirstOrDefault(p => p.Instance != null); + if (plugin == null) + { + // Then by the status. + plugin = plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); + } + + _installationManager.UninstallPlugin(plugin!); + return NoContent(); + } + /// /// Gets plugin configuration. /// @@ -281,33 +310,6 @@ namespace Jellyfin.Api.Controllers return PhysicalFile(imgPath, MimeTypes.GetMimeType(imgPath)); } - /// - /// Gets a plugin's status image. - /// - /// Plugin id. - /// Plugin version. - /// Plugin image returned. - /// Plugin's image. - [HttpGet("{pluginId}/{version}/StatusImage")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesImageFile] - [AllowAnonymous] - public ActionResult GetPluginStatusImage([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) - { - if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) - { - return NotFound(); - } - - // Icons from http://www.fatcow.com/free-icons - var status = plugin!.Manifest.Status; - - var type = _pluginManager.GetType(); - var stream = type.Assembly.GetManifestResourceStream($"{type.Namespace}.Plugins.{status}.png"); - return File(stream, "image/png"); - } - /// /// Gets a plugin's manifest. /// From 208d545cfefd5ce7a2092f4ac669e58cae115d37 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 10:05:04 +0000 Subject: [PATCH 15/77] Changed as suggested. --- .../Plugins/PluginManager.cs | 27 ++++++++------- .../Updates/InstallationManager.cs | 3 +- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- MediaBrowser.Common/Json/JsonDefaults.cs | 1 + MediaBrowser.Model/Plugins/PluginStatus.cs | 33 +++++++++++++++++-- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index cf25ccf485..010d2829c4 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -53,9 +53,7 @@ namespace Emby.Server.Implementations _logger = loggerfactory.CreateLogger(); _pluginsPath = pluginsPath; _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); - _jsonOptions = JsonDefaults.GetOptions(); - _jsonOptions.PropertyNameCaseInsensitive = true; - _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + _jsonOptions = JsonDefaults.GetCamelCaseOptions(); _config = config; _appHost = appHost; _imagesPath = imagesPath; @@ -137,7 +135,8 @@ namespace Emby.Server.Implementations var plugin = GetPluginByType(pluginServiceRegistrator.Assembly.GetType()); if (plugin == null) { - throw new NullReferenceException(); + _logger.LogError("Unable to find plugin in assembly {Assembly}", pluginServiceRegistrator.Assembly.FullName); + continue; } CheckIfStillSuperceded(plugin); @@ -440,6 +439,7 @@ namespace Emby.Server.Implementations plugin.Instance = (IPlugin)instance; var manifest = plugin.Manifest; var pluginStr = plugin.Instance.Version.ToString(); + bool changed = false; if (string.Equals(manifest.Version, pluginStr, StringComparison.Ordinal)) { // If a plugin without a manifest failed to load due to an external issue (eg config), @@ -447,10 +447,16 @@ namespace Emby.Server.Implementations manifest.Version = pluginStr; manifest.Name = plugin.Instance.Name; manifest.Description = plugin.Instance.Description; + changed = true; } + changed = changed || manifest.Status != PluginStatus.Active; manifest.Status = PluginStatus.Active; - SaveManifest(manifest, plugin.Path); + + if (changed) + { + SaveManifest(manifest, plugin.Path); + } } _logger.LogInformation("Loaded plugin: {PluginName} {PluginVersion}", plugin.Name, plugin.Version); @@ -577,8 +583,6 @@ namespace Emby.Server.Implementations } // Auto-create a plugin manifest, so we can disable it, if it fails to load. - // NOTE: This Plugin is marked as valid for two upgrades, at which point, it can be assumed the - // code base will have changed sufficiently to make it invalid. manifest = new PluginManifest { Status = PluginStatus.RestartRequired, @@ -586,7 +590,6 @@ namespace Emby.Server.Implementations AutoUpdate = false, Guid = metafile.GetMD5(), TargetAbi = _appVersion.ToString(), - MaxAbi = _nextVersion.ToString(), Version = version.ToString() }; @@ -678,9 +681,11 @@ namespace Emby.Server.Implementations continue; } - manifest.Status = PluginStatus.DeleteOnStartup; - - SaveManifest(manifest, entry.Path); + if (manifest.Status != PluginStatus.DeleteOnStartup) + { + manifest.Status = PluginStatus.DeleteOnStartup; + SaveManifest(manifest, entry.Path); + } } } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 75a9ca0801..b7bbbd3482 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -93,8 +93,7 @@ namespace Emby.Server.Implementations.Updates _httpClientFactory = httpClientFactory; _config = config; _zipClient = zipClient; - _jsonSerializerOptions = JsonDefaults.GetOptions(); - _jsonSerializerOptions.PropertyNameCaseInsensitive = true; + _jsonSerializerOptions = JsonDefaults.GetCamelCaseOptions(); _pluginManager = pluginManager; } diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index c84dc6a130..eb6b770d67 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers { _installationManager = installationManager; _pluginManager = pluginManager; - _serializerOptions = JsonDefaults.GetOptions(); + _serializerOptions = JsonDefaults.GetCamelCaseOptions(); _config = config; } diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index b76edd2bc7..50393b909a 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -56,6 +56,7 @@ namespace MediaBrowser.Common.Json { var options = GetOptions(); options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.PropertyNameCaseInsensitive = true; return options; } diff --git a/MediaBrowser.Model/Plugins/PluginStatus.cs b/MediaBrowser.Model/Plugins/PluginStatus.cs index a953206e89..2acc56811e 100644 --- a/MediaBrowser.Model/Plugins/PluginStatus.cs +++ b/MediaBrowser.Model/Plugins/PluginStatus.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -#pragma warning disable SA1602 // Enumeration items should be documented namespace MediaBrowser.Model.Plugins { /// @@ -7,12 +5,43 @@ namespace MediaBrowser.Model.Plugins /// public enum PluginStatus { + /// + /// This plugin requires a restart in order for it to load. This is a memory only status. + /// The actual status of the plugin after reload is present in the manifest. + /// eg. A disabled plugin will still be active until the next restart, and so will have a memory status of RestartRequired, + /// but a disk manifest status of Disabled. + /// RestartRequired = 1, + + /// + /// This plugin is currently running. + /// Active = 0, + + /// + /// This plugin has been marked as disabled. + /// Disabled = -1, + + /// + /// This plugin does not meet the TargetAbi / MaxAbi requirements. + /// NotSupported = -2, + + /// + /// This plugin caused an error when instantiated. (Either DI loop, or exception) + /// Malfunction = -3, + + /// + /// This plugin has been superceded by another version. + /// Superceded = -4, + + /// + /// An attempt to remove this plugin from disk will happen at every restart. + /// It will not be loaded, if unable to do so. + /// DeleteOnStartup = -5 } } From c761cbff0e2d8bbf6b348db09c664760c4ccd1eb Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 10:20:28 +0000 Subject: [PATCH 16/77] more changes. --- MediaBrowser.Model/Plugins/PluginInfo.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Model/Plugins/PluginInfo.cs b/MediaBrowser.Model/Plugins/PluginInfo.cs index 52c99b9c3e..25216610d4 100644 --- a/MediaBrowser.Model/Plugins/PluginInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginInfo.cs @@ -20,9 +20,9 @@ namespace MediaBrowser.Model.Plugins public PluginInfo(string name, Version version, string description, Guid id, bool canUninstall) { Name = name; - Version = version?.ToString() ?? throw new ArgumentNullException(nameof(version)); + Version = version; Description = description; - Id = id.ToString(); + Id = id; CanUninstall = canUninstall; } @@ -34,7 +34,7 @@ namespace MediaBrowser.Model.Plugins /// /// Gets or sets the version. /// - public string Version { get; set; } + public Version Version { get; set; } /// /// Gets or sets the name of the configuration file. @@ -49,7 +49,7 @@ namespace MediaBrowser.Model.Plugins /// /// Gets or sets the unique id. /// - public string Id { get; set; } + public Guid Id { get; set; } /// /// Gets or sets a value indicating whether the plugin can be uninstalled. From eb2439f23b05a9b92a81ee96c7801d10ccfbb25d Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 16:37:11 +0000 Subject: [PATCH 17/77] Changes as recommended. --- .../ApplicationHost.cs | 2 +- .../Plugins/PluginManager.cs | 59 ++++++++----------- .../Updates/InstallationManager.cs | 15 +---- Jellyfin.Api/Controllers/PackageController.cs | 2 - MediaBrowser.Common/Plugins/LocalPlugin.cs | 5 -- MediaBrowser.Model/Updates/PackageInfo.cs | 9 +++ 6 files changed, 35 insertions(+), 57 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 17cccdaf92..216cb5e758 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -396,7 +396,7 @@ namespace Emby.Server.Implementations Logger.LogError("DI Loop detected in the attempted creation of {Type}", type.FullName); foreach (var entry in _creatingInstances) { - Logger.LogError("Called from: {stack}", entry.FullName); + Logger.LogError("Called from: {TypeName}", entry.FullName); } _pluginManager.FailPlugin(type.Assembly); diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 010d2829c4..944b746528 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -96,9 +96,10 @@ namespace Emby.Server.Implementations foreach (var file in plugin.DllFiles) { + Assembly assembly; try { - plugin.Assembly = Assembly.LoadFrom(file); + assembly = Assembly.LoadFrom(file); } catch (FileLoadException ex) { @@ -107,8 +108,8 @@ namespace Emby.Server.Implementations continue; } - _logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugin.Assembly.FullName, file); - yield return plugin.Assembly; + _logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, file); + yield return assembly; } } } @@ -203,6 +204,7 @@ namespace Emby.Server.Implementations return true; } + _logger.LogWarning("Unable to delete {Path}, so marking as deleteOnStartup.", plugin.Path); // Unable to delete, so disable. return ChangePluginState(plugin, PluginStatus.DeleteOnStartup); } @@ -310,10 +312,7 @@ namespace Emby.Server.Implementations throw new ArgumentNullException(nameof(assembly)); } - var plugin = _plugins.Where( - p => assembly.Equals(p.Assembly) - || string.Equals(assembly.Location, assembly.Location, StringComparison.OrdinalIgnoreCase)) - .FirstOrDefault(); + var plugin = _plugins.Where(p => p.DllFiles.Contains(assembly.Location)).FirstOrDefault(); if (plugin == null) { // A plugin's assembly didn't cause this issue, so ignore it. @@ -366,20 +365,7 @@ namespace Emby.Server.Implementations } plugin.Manifest.Status = state; - SaveManifest(plugin.Manifest, plugin.Path); - try - { - var data = JsonSerializer.Serialize(plugin.Manifest, _jsonOptions); - File.WriteAllText(Path.Combine(plugin.Path, "meta.json"), data, Encoding.UTF8); - return true; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception e) -#pragma warning restore CA1031 // Do not catch general exception types - { - _logger.LogWarning(e, "Unable to disable plugin {Path}", plugin.Path); - return false; - } + return SaveManifest(plugin.Manifest, plugin.Path); } /// @@ -509,15 +495,14 @@ namespace Emby.Server.Implementations // Attempt a cleanup of old folders. try { - _logger.LogDebug("Deleting {Path}", plugin.Path); Directory.Delete(plugin.Path, true); + _logger.LogDebug("Deleted {Path}", plugin.Path); _plugins.Remove(plugin); } #pragma warning disable CA1031 // Do not catch general exception types - catch (Exception e) + catch #pragma warning restore CA1031 // Do not catch general exception types { - _logger.LogWarning(e, "Unable to delete {Path}", plugin.Path); return false; } @@ -670,21 +655,23 @@ namespace Emby.Server.Implementations _logger.LogWarning(e, "Unable to delete {Path}", path); } - versions.RemoveAt(x); - } - - if (!cleaned) - { - if (manifest == null) + if (cleaned) { - _logger.LogWarning("Unable to disable plugin {Path}", entry.Path); - continue; + versions.RemoveAt(x); } - - if (manifest.Status != PluginStatus.DeleteOnStartup) + else { - manifest.Status = PluginStatus.DeleteOnStartup; - SaveManifest(manifest, entry.Path); + if (manifest == null) + { + _logger.LogWarning("Unable to disable plugin {Path}", entry.Path); + continue; + } + + if (manifest.Status != PluginStatus.DeleteOnStartup) + { + manifest.Status = PluginStatus.DeleteOnStartup; + SaveManifest(manifest, entry.Path); + } } } } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index b7bbbd3482..fc80bdd756 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Json; using System.Security.Cryptography; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -106,18 +105,8 @@ namespace Emby.Server.Implementations.Updates try { List? packages; - var uri = new Uri(manifest); - if (uri.Scheme.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - packages = await _httpClientFactory.CreateClient(NamedClient.Default) - .GetFromJsonAsync>(uri, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); - } - else - { - // Local Packages - var data = File.ReadAllText(manifest, Encoding.UTF8); - packages = JsonSerializer.Deserialize>(data, _jsonSerializerOptions); - } + packages = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetFromJsonAsync>(new Uri(manifest), _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); if (packages == null) { diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index 906188026b..9ab8e0bdcf 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -44,7 +44,6 @@ namespace Jellyfin.Api.Controllers /// A containing package information. [HttpGet("Packages/{name}")] [ProducesResponseType(StatusCodes.Status200OK)] - [Produces(JsonDefaults.CamelCaseMediaType)] public async Task> GetPackageInfo( [FromRoute, Required] string name, [FromQuery] Guid? assemblyGuid) @@ -71,7 +70,6 @@ namespace Jellyfin.Api.Controllers /// An containing available packages information. [HttpGet("Packages")] [ProducesResponseType(StatusCodes.Status200OK)] - [Produces(JsonDefaults.CamelCaseMediaType)] public async Task> GetPackages() { IEnumerable packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index ef9ab7a7dc..e48ebbfa5c 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -80,11 +80,6 @@ namespace MediaBrowser.Common.Plugins /// public PluginManifest Manifest { get; } - /// - /// Gets or sets a value indicating the assembly of the plugin. - /// - public Assembly? Assembly { get; set; } - /// /// Compare two . /// diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index 77e2d8d88a..63fd717429 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -1,6 +1,7 @@ #nullable enable using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace MediaBrowser.Model.Updates { @@ -27,30 +28,35 @@ namespace MediaBrowser.Model.Updates /// Gets or sets the name. /// /// The name. + [JsonPropertyName("name")] public string Name { get; set; } /// /// Gets or sets a long description of the plugin containing features or helpful explanations. /// /// The description. + /// [JsonPropertyName("description")] public string Description { get; set; } /// /// Gets or sets a short overview of what the plugin does. /// /// The overview. + [JsonPropertyName("overview")] public string Overview { get; set; } /// /// Gets or sets the owner. /// /// The owner. + [JsonPropertyName("owner")] public string Owner { get; set; } /// /// Gets or sets the category. /// /// The category. + [JsonPropertyName("category")] public string Category { get; set; } /// @@ -58,6 +64,7 @@ namespace MediaBrowser.Model.Updates /// This is used to identify the proper item for automatic updates. /// /// The name. + [JsonPropertyName("guid")] #pragma warning disable CA1720 // Identifier contains type name public string Guid { get; set; } #pragma warning restore CA1720 // Identifier contains type name @@ -66,6 +73,7 @@ namespace MediaBrowser.Model.Updates /// Gets or sets the versions. /// /// The versions. + [JsonPropertyName("versions")] #pragma warning disable CA2227 // Collection properties should be read only public IList Versions { get; set; } #pragma warning restore CA2227 // Collection properties should be read only @@ -73,6 +81,7 @@ namespace MediaBrowser.Model.Updates /// /// Gets or sets the image url for the package. /// + [JsonPropertyName("imageUrl")] public string? ImageUrl { get; set; } } } From c197dca759ee7353d7e9f477b3cc189eac0c14e0 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 18:27:31 +0000 Subject: [PATCH 18/77] Changed PluginId to guid so its the same type as plugin.id --- Jellyfin.Api/Models/ConfigurationPageInfo.cs | 9 ++++----- MediaBrowser.Model/Updates/VersionInfo.cs | 12 +++++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index 3c553a1b50..c15ed05d39 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -1,3 +1,4 @@ +using System; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; @@ -23,7 +24,7 @@ namespace Jellyfin.Api.Models { DisplayName = page.Plugin.Name; // Don't use "N" because it needs to match Plugin.Id - PluginId = page.Plugin.Id.ToString(); + PluginId = page.Plugin.Id; } } @@ -39,9 +40,7 @@ namespace Jellyfin.Api.Models MenuSection = page.MenuSection; MenuIcon = page.MenuIcon; DisplayName = string.IsNullOrWhiteSpace(page.DisplayName) ? plugin?.Name : page.DisplayName; - - // Don't use "N" because it needs to match Plugin.Id - PluginId = plugin?.Id.ToString(); + PluginId = plugin?.Id; } /// @@ -80,6 +79,6 @@ namespace Jellyfin.Api.Models /// Gets or sets the plugin id. /// /// The plugin id. - public string? PluginId { get; set; } + public Guid? PluginId { get; set; } } } diff --git a/MediaBrowser.Model/Updates/VersionInfo.cs b/MediaBrowser.Model/Updates/VersionInfo.cs index 1e07c9f26b..503dba0a14 100644 --- a/MediaBrowser.Model/Updates/VersionInfo.cs +++ b/MediaBrowser.Model/Updates/VersionInfo.cs @@ -1,11 +1,12 @@ #nullable enable +using System.Text.Json.Serialization; using SysVersion = System.Version; namespace MediaBrowser.Model.Updates { /// - /// Class PackageVersionInfo. + /// Defines the class. /// public class VersionInfo { @@ -15,6 +16,7 @@ namespace MediaBrowser.Model.Updates /// Gets or sets the version. /// /// The version. + [JsonPropertyName("version")] public string Version { get => _version == null ? string.Empty : _version.ToString(); @@ -31,46 +33,54 @@ namespace MediaBrowser.Model.Updates /// Gets or sets the changelog for this version. /// /// The changelog. + [JsonPropertyName("changelog")] public string? Changelog { get; set; } /// /// Gets or sets the ABI that this version was built against. /// /// The target ABI version. + [JsonPropertyName("targetAbi")] public string? TargetAbi { get; set; } /// /// Gets or sets the maximum ABI that this version will work with. /// /// The target ABI version. + [JsonPropertyName("maxAbi")] public string? MaxAbi { get; set; } /// /// Gets or sets the source URL. /// /// The source URL. + [JsonPropertyName("sourceUrl")] public string? SourceUrl { get; set; } /// /// Gets or sets a checksum for the binary. /// /// The checksum. + [JsonPropertyName("checksum")] public string? Checksum { get; set; } /// /// Gets or sets a timestamp of when the binary was built. /// /// The timestamp. + [JsonPropertyName("timestamp")] public string? Timestamp { get; set; } /// /// Gets or sets the repository name. /// + [JsonPropertyName("repositoryName")] public string RepositoryName { get; set; } = string.Empty; /// /// Gets or sets the repository url. /// + [JsonPropertyName("repositoryUrl")] public string RepositoryUrl { get; set; } = string.Empty; } } From 3f1ad7f963f4d631861d8b6ac30acd7e4753ccf0 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 19:22:54 +0000 Subject: [PATCH 19/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index eb6b770d67..9b731f88a9 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -200,7 +200,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [Obsolete("Please use the UninstallByVersion API.")] + [Obsolete("Please use the UninstallPluginByVersion API.")] public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId) { // If no version is given, return the current instance. From 2afa963fc134853e86ab30029be3ea22e9c72366 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 19:24:39 +0000 Subject: [PATCH 20/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 9b731f88a9..1e658890eb 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -127,7 +127,7 @@ namespace Jellyfin.Api.Controllers /// Plugin version. /// Plugin enabled. /// Plugin not found. - /// An on success, or a if the file could not be found. + /// An on success, or a if the plugin could not be found. [HttpPost("{pluginId}/{version}/Enable")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] From e4993ae574b0b01304297770e58bb9b86e34ef6e Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 19:24:52 +0000 Subject: [PATCH 21/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 1e658890eb..9a4d0c40b7 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -173,7 +173,7 @@ namespace Jellyfin.Api.Controllers /// Plugin version. /// Plugin uninstalled. /// Plugin not found. - /// An on success, or a if the file could not be found. + /// An on success, or a if the plugin could not be found. [HttpDelete("{pluginId}/{version}")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] From 24ab152e9d340e041dc57cfd6dbd60d6d35f9cc9 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 19:30:23 +0000 Subject: [PATCH 22/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 9a4d0c40b7..c3c9460e65 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -150,7 +150,7 @@ namespace Jellyfin.Api.Controllers /// Plugin version. /// Plugin disabled. /// Plugin not found. - /// An on success, or a if the file could not be found. + /// An on success, or a if the plugin could not be found. [HttpPost("{pluginId}/{version}/Disable")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] From aecd35d30668595b761e878265bb0ed61826ec50 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 15 Dec 2020 19:31:36 +0000 Subject: [PATCH 23/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index c3c9460e65..565bf23119 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -324,7 +324,6 @@ namespace Jellyfin.Api.Controllers [HttpPost("{pluginId}/Manifest")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesFile(MediaTypeNames.Application.Json)] public ActionResult GetPluginManifest([FromRoute, Required] Guid pluginId) { if (_pluginManager.TryGetPlugin(pluginId, null, out var plugin)) From 00ff3b90962ff616629fab675c6e0108d3af58ae Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 19:35:26 +0000 Subject: [PATCH 24/77] remove attribute --- Jellyfin.Api/Controllers/PluginsController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 565bf23119..b5976fbbdf 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -228,7 +228,6 @@ namespace Jellyfin.Api.Controllers [HttpGet("{pluginId}/Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesFile(MediaTypeNames.Application.Json)] public ActionResult GetPluginConfiguration([FromRoute, Required] Guid pluginId) { if (_pluginManager.TryGetPlugin(pluginId, null, out var plugin) From 0337e39bae80ba78d19821373603f19bd6a5a95f Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 19:39:41 +0000 Subject: [PATCH 25/77] Updated JsonDefaults --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 1 + MediaBrowser.Common/Json/JsonDefaults.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index cd594b5c5e..bbfc4fbd4c 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -227,6 +227,7 @@ namespace Jellyfin.Server.Extensions options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented; options.JsonSerializerOptions.DefaultIgnoreCondition = jsonOptions.DefaultIgnoreCondition; options.JsonSerializerOptions.NumberHandling = jsonOptions.NumberHandling; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = jsonOptions.PropertyNameCaseInsensitive; options.JsonSerializerOptions.Converters.Clear(); foreach (var converter in jsonOptions.Converters) diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 50393b909a..f232ebdc7f 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -36,7 +36,8 @@ namespace MediaBrowser.Common.Json ReadCommentHandling = JsonCommentHandling.Disallow, WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - NumberHandling = JsonNumberHandling.AllowReadingFromString + NumberHandling = JsonNumberHandling.AllowReadingFromString, + PropertyNameCaseInsensitive = true }; options.Converters.Add(new JsonGuidConverter()); @@ -56,7 +57,6 @@ namespace MediaBrowser.Common.Json { var options = GetOptions(); options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.PropertyNameCaseInsensitive = true; return options; } From 532388754053957a1b3066900b7b54e1894206f3 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 20:27:42 +0000 Subject: [PATCH 26/77] Replaced TryGetPlugin with GetPlugin --- .../Plugins/PluginManager.cs | 13 +++---- .../Updates/InstallationManager.cs | 5 +-- Jellyfin.Api/Controllers/PluginsController.cs | 34 +++++++++++-------- MediaBrowser.Common/Plugins/IPluginManager.cs | 5 ++- 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 944b746528..26a029f712 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -212,12 +212,13 @@ namespace Emby.Server.Implementations /// /// Attempts to find the plugin with and id of . /// - /// Id of plugin. - /// The version of the plugin to locate. - /// A if found, otherwise null. - /// Boolean value signifying the success of the search. - public bool TryGetPlugin(Guid id, Version? version, out LocalPlugin? plugin) + /// The of plugin. + /// Optional of the plugin to locate. + /// A if located, or null if not. + public LocalPlugin? GetPlugin(Guid id, Version? version = null) { + LocalPlugin? plugin; + if (version == null) { // If no version is given, return the current instance. @@ -235,7 +236,7 @@ namespace Emby.Server.Implementations plugin = _plugins.FirstOrDefault(p => p.Id.Equals(id) && p.Version.Equals(version)); } - return plugin != null; + return plugin; } /// diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index fc80bdd756..7cab77c85d 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -197,10 +197,11 @@ namespace Emby.Server.Implementations.Updates { var version = package.Versions[i]; + var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber); // Update the manifests, if anything changes. - if (_pluginManager.TryGetPlugin(packageGuid, version.VersionNumber, out LocalPlugin? plugin)) + if (plugin != null) { - bool noChange = string.Equals(plugin!.Manifest.MaxAbi, version.MaxAbi, StringComparison.Ordinal) + bool noChange = string.Equals(plugin.Manifest.MaxAbi, version.MaxAbi, StringComparison.Ordinal) || string.Equals(plugin.Manifest.TargetAbi, version.TargetAbi, StringComparison.Ordinal); if (!noChange) { diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index b5976fbbdf..49ccac1375 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -134,12 +134,13 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult EnablePlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { - if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) + var plugin = _pluginManager.GetPlugin(pluginId, version); + if (plugin == null) { return NotFound(); } - _pluginManager.EnablePlugin(plugin!); + _pluginManager.EnablePlugin(plugin); return NoContent(); } @@ -157,12 +158,13 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult DisablePlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { - if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) + var plugin = _pluginManager.GetPlugin(pluginId, version); + if (plugin == null) { return NotFound(); } - _pluginManager.DisablePlugin(plugin!); + _pluginManager.DisablePlugin(plugin); return NoContent(); } @@ -180,7 +182,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UninstallPluginByVersion([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { - if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) + var plugin = _pluginManager.GetPlugin(pluginId, version); + if (plugin == null) { return NotFound(); } @@ -230,8 +233,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetPluginConfiguration([FromRoute, Required] Guid pluginId) { - if (_pluginManager.TryGetPlugin(pluginId, null, out var plugin) - && plugin!.Instance is IHasPluginConfiguration configPlugin) + var plugin = _pluginManager.GetPlugin(pluginId); + if (plugin?.Instance is IHasPluginConfiguration configPlugin) { return configPlugin.Configuration; } @@ -258,8 +261,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdatePluginConfiguration([FromRoute, Required] Guid pluginId) { - if (!_pluginManager.TryGetPlugin(pluginId, null, out var plugin) - || plugin?.Instance is not IHasPluginConfiguration configPlugin) + var plugin = _pluginManager.GetPlugin(pluginId); + if (plugin?.Instance is not IHasPluginConfiguration configPlugin) { return NotFound(); } @@ -289,14 +292,15 @@ namespace Jellyfin.Api.Controllers [AllowAnonymous] public ActionResult GetPluginImage([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version) { - if (!_pluginManager.TryGetPlugin(pluginId, version, out var plugin)) + var plugin = _pluginManager.GetPlugin(pluginId, version); + if (plugin == null) { return NotFound(); } - var imgPath = Path.Combine(plugin!.Path, plugin!.Manifest.ImageUrl ?? string.Empty); + var imgPath = Path.Combine(plugin.Path, plugin.Manifest.ImageUrl ?? string.Empty); if (((ServerConfiguration)_config.CommonConfiguration).DisablePluginImages - || plugin!.Manifest.ImageUrl == null + || plugin.Manifest.ImageUrl == null || !System.IO.File.Exists(imgPath)) { // Use a blank image. @@ -325,9 +329,11 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetPluginManifest([FromRoute, Required] Guid pluginId) { - if (_pluginManager.TryGetPlugin(pluginId, null, out var plugin)) + var plugin = _pluginManager.GetPlugin(pluginId); + + if (plugin != null) { - return Ok(plugin!.Manifest); + return Ok(plugin.Manifest); } return NotFound(); diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index 071b51969f..7f7381b033 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -72,9 +72,8 @@ namespace MediaBrowser.Common.Plugins /// /// Id of plugin. /// The version of the plugin to locate. - /// A if found, otherwise null. - /// Boolean value signifying the success of the search. - bool TryGetPlugin(Guid id, Version? version, out LocalPlugin? plugin); + /// A if located, or null if not. + LocalPlugin? GetPlugin(Guid id, Version? version = null); /// /// Removes the plugin. From 6d3e1d6b5726201d7c943ba007ef873810ffd7bc Mon Sep 17 00:00:00 2001 From: Greenback Date: Wed, 16 Dec 2020 19:37:23 +0000 Subject: [PATCH 27/77] Small Optimization --- MediaBrowser.Common/Plugins/LocalPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index e48ebbfa5c..8aded8b9bc 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.Common.Plugins throw new ArgumentNullException(a == null ? nameof(a) : nameof(b)); } - var compare = string.Compare(a.Name, b.Name, true, CultureInfo.InvariantCulture); + var compare = string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); // Id is not equal but name is. if (!a.Id.Equals(b.Id) && compare == 0) From ebbb57efc3274663b515b4accc52f4a4e9920e77 Mon Sep 17 00:00:00 2001 From: Greenback Date: Wed, 16 Dec 2020 21:40:52 +0000 Subject: [PATCH 28/77] Change json default settings. --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 49ccac1375..4f65e18e15 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers { _installationManager = installationManager; _pluginManager = pluginManager; - _serializerOptions = JsonDefaults.GetCamelCaseOptions(); + _serializerOptions = JsonDefaults.GetOptions(); _config = config; } From 1ed25ebd9a11f3fc1838e347a34621dcde0b7bd5 Mon Sep 17 00:00:00 2001 From: Greenback Date: Wed, 16 Dec 2020 22:36:25 +0000 Subject: [PATCH 29/77] Corrections as recommended. --- .../Updates/InstallationManager.cs | 5 ++--- Jellyfin.Api/Controllers/PluginsController.cs | 16 +++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 7cab77c85d..70424369bd 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Updates _httpClientFactory = httpClientFactory; _config = config; _zipClient = zipClient; - _jsonSerializerOptions = JsonDefaults.GetCamelCaseOptions(); + _jsonSerializerOptions = JsonDefaults.GetOptions(); _pluginManager = pluginManager; } @@ -104,8 +104,7 @@ namespace Emby.Server.Implementations.Updates { try { - List? packages; - packages = await _httpClientFactory.CreateClient(NamedClient.Default) + List? packages = await _httpClientFactory.CreateClient(NamedClient.Default) .GetFromJsonAsync>(new Uri(manifest), _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); if (packages == null) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 4f65e18e15..6db74571cc 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -217,8 +217,13 @@ namespace Jellyfin.Api.Controllers plugin = plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); } - _installationManager.UninstallPlugin(plugin!); - return NoContent(); + if (plugin != null) + { + _installationManager.UninstallPlugin(plugin!); + return NoContent(); + } + + return NotFound(); } /// @@ -303,10 +308,7 @@ namespace Jellyfin.Api.Controllers || plugin.Manifest.ImageUrl == null || !System.IO.File.Exists(imgPath)) { - // Use a blank image. - var type = GetType(); - var stream = type.Assembly.GetManifestResourceStream(type.Namespace + ".Plugins.blank.png"); - return File(stream, "image/png"); + return NotFound(); } imgPath = Path.Combine(plugin.Path, plugin.Manifest.ImageUrl); @@ -333,7 +335,7 @@ namespace Jellyfin.Api.Controllers if (plugin != null) { - return Ok(plugin.Manifest); + return plugin.Manifest; } return NotFound(); From d9aaba36ec807b12d58fdf9498ee60574ba44a54 Mon Sep 17 00:00:00 2001 From: Greenback Date: Wed, 16 Dec 2020 23:19:09 +0000 Subject: [PATCH 30/77] Copy previous plugin settings if they don't exist. --- .../Plugins/PluginManager.cs | 29 +++++++++++++++++++ MediaBrowser.Common/Plugins/BasePlugin.cs | 4 +-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 26a029f712..e7589a0f92 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -274,6 +274,32 @@ namespace Emby.Server.Implementations } } + private void CopyFiles(string source, string destination, bool overwrite, string searchPattern) + { + FileInfo[] files; + try + { + files = new DirectoryInfo(source).GetFiles(searchPattern); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error retrieving file list."); + return; + } + + foreach (FileInfo file in files) + { + try + { + file.CopyTo(Path.Combine(destination, file.Name), overwrite); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error copying file {Name}", file.Name); + } + } + } + /// /// Changes the status of the other versions of the plugin to "Superceded". /// @@ -295,6 +321,9 @@ namespace Emby.Server.Implementations return; } + // migrate settings across from the last active version if they don't exist. + CopyFiles(predecessor.Path, plugin.Path, false, "*.xml"); + if (predecessor.Manifest.Status == PluginStatus.Active && !ChangePluginState(predecessor, PluginStatus.Superceded)) { _logger.LogError("Unable to disable version {Version} of {Name}", predecessor.Version, predecessor.Name); diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index a0d6b8f836..5756f8852c 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -166,14 +166,14 @@ namespace MediaBrowser.Common.Plugins assemblyPlugin.SetId(assemblyId); } - // TODO : Simplify this, once migration support is ceased. + // TODO : Remove this, once migration support is ceased. if (inPluginFolder) { var oldConfigFilePath = Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName); if (!File.Exists(ConfigurationFilePath) && File.Exists(oldConfigFilePath)) { - // Migrate settings, as different plugin versions may have different settings. + // Migrate pre 10.7 settings, as different plugin versions may have different settings. try { File.Copy(oldConfigFilePath, ConfigurationFilePath); From 212c76102daa679e6bba387a5501ae0092e13fb5 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 17 Dec 2020 13:37:13 +0000 Subject: [PATCH 31/77] Update PluginManifest.cs --- MediaBrowser.Common/Plugins/PluginManifest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index b88275718a..1bd17933c4 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -67,7 +67,7 @@ namespace MediaBrowser.Common.Plugins public string Version { get; set; } = string.Empty; /// - /// Gets or sets a value indicating whether this plugin should be ignored. + /// Gets or sets a value indicating the operational status of this plugin. /// public PluginStatus Status { get; set; } From a4a40407a047de2f10aa0f9d0ba9fe0f600ffdb0 Mon Sep 17 00:00:00 2001 From: Greenback Date: Thu, 17 Dec 2020 13:44:38 +0000 Subject: [PATCH 32/77] Change PluginStatus states. --- .../Plugins/PluginManager.cs | 20 +++++++++---------- MediaBrowser.Model/Plugins/PluginStatus.cs | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index e7589a0f92..975b708435 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations for (int a = _plugins.Count - 1; a >= 0; a--) { var plugin = _plugins[a]; - if (plugin.Manifest.Status == PluginStatus.DeleteOnStartup && DeletePlugin(plugin)) + if (plugin.Manifest.Status == PluginStatus.Deleted && DeletePlugin(plugin)) { UpdateSuccessors(plugin); } @@ -104,7 +104,7 @@ namespace Emby.Server.Implementations catch (FileLoadException ex) { _logger.LogError(ex, "Failed to load assembly {Path}. Disabling plugin.", file); - ChangePluginState(plugin, PluginStatus.Malfunction); + ChangePluginState(plugin, PluginStatus.Malfunctioned); continue; } @@ -156,7 +156,7 @@ namespace Emby.Server.Implementations #pragma warning restore CA1031 // Do not catch general exception types { _logger.LogError(ex, "Error registering plugin services from {Assembly}.", pluginServiceRegistrator.Assembly.FullName); - if (ChangePluginState(plugin, PluginStatus.Malfunction)) + if (ChangePluginState(plugin, PluginStatus.Malfunctioned)) { _logger.LogInformation("Disabling plugin {Path}", plugin.Path); } @@ -206,7 +206,7 @@ namespace Emby.Server.Implementations _logger.LogWarning("Unable to delete {Path}, so marking as deleteOnStartup.", plugin.Path); // Unable to delete, so disable. - return ChangePluginState(plugin, PluginStatus.DeleteOnStartup); + return ChangePluginState(plugin, PluginStatus.Deleted); } /// @@ -307,7 +307,7 @@ namespace Emby.Server.Implementations private void UpdateSuccessors(LocalPlugin plugin) { // This value is memory only - so that the web will show restart required. - plugin.Manifest.Status = PluginStatus.RestartRequired; + plugin.Manifest.Status = PluginStatus.Restart; // Detect whether there is another version of this plugin that needs disabling. var predecessor = _plugins.OrderByDescending(p => p.Version) @@ -349,7 +349,7 @@ namespace Emby.Server.Implementations return; } - ChangePluginState(plugin, PluginStatus.Malfunction); + ChangePluginState(plugin, PluginStatus.Malfunctioned); } /// @@ -486,7 +486,7 @@ namespace Emby.Server.Implementations _logger.LogError(ex, "Error creating {Type}", type.FullName); if (plugin != null) { - if (ChangePluginState(plugin, PluginStatus.Malfunction)) + if (ChangePluginState(plugin, PluginStatus.Malfunctioned)) { _logger.LogInformation("Plugin {Path} has been disabled.", plugin.Path); return null; @@ -600,7 +600,7 @@ namespace Emby.Server.Implementations // Auto-create a plugin manifest, so we can disable it, if it fails to load. manifest = new PluginManifest { - Status = PluginStatus.RestartRequired, + Status = PluginStatus.Restart, Name = metafile, AutoUpdate = false, Guid = metafile.GetMD5(), @@ -697,9 +697,9 @@ namespace Emby.Server.Implementations continue; } - if (manifest.Status != PluginStatus.DeleteOnStartup) + if (manifest.Status != PluginStatus.Deleted) { - manifest.Status = PluginStatus.DeleteOnStartup; + manifest.Status = PluginStatus.Deleted; SaveManifest(manifest, entry.Path); } } diff --git a/MediaBrowser.Model/Plugins/PluginStatus.cs b/MediaBrowser.Model/Plugins/PluginStatus.cs index 2acc56811e..3ea05174fd 100644 --- a/MediaBrowser.Model/Plugins/PluginStatus.cs +++ b/MediaBrowser.Model/Plugins/PluginStatus.cs @@ -8,10 +8,10 @@ namespace MediaBrowser.Model.Plugins /// /// This plugin requires a restart in order for it to load. This is a memory only status. /// The actual status of the plugin after reload is present in the manifest. - /// eg. A disabled plugin will still be active until the next restart, and so will have a memory status of RestartRequired, + /// eg. A disabled plugin will still be active until the next restart, and so will have a memory status of Restart, /// but a disk manifest status of Disabled. /// - RestartRequired = 1, + Restart = 1, /// /// This plugin is currently running. @@ -31,7 +31,7 @@ namespace MediaBrowser.Model.Plugins /// /// This plugin caused an error when instantiated. (Either DI loop, or exception) /// - Malfunction = -3, + Malfunctioned = -3, /// /// This plugin has been superceded by another version. @@ -42,6 +42,6 @@ namespace MediaBrowser.Model.Plugins /// An attempt to remove this plugin from disk will happen at every restart. /// It will not be loaded, if unable to do so. /// - DeleteOnStartup = -5 + Deleted = -5 } } From 0ed3c2def2017f775cb9ab3e31aa9aa95cdea7f4 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 17 Dec 2020 13:50:24 +0000 Subject: [PATCH 33/77] Update MediaBrowser.sln --- MediaBrowser.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 36518377c4..c654e8ef36 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30503.244 From e445c2932afe8107846f85e308941d75350be013 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:23:15 +0000 Subject: [PATCH 34/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 6db74571cc..5a3a3eef9f 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -219,7 +219,7 @@ namespace Jellyfin.Api.Controllers if (plugin != null) { - _installationManager.UninstallPlugin(plugin!); + _installationManager.UninstallPlugin(plugin); return NoContent(); } From 5d5b198525bd3a45f6c3f61dda559597e24b7d45 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:23:28 +0000 Subject: [PATCH 35/77] Update Jellyfin.Api/Controllers/PluginsController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 5a3a3eef9f..1365764fba 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - _installationManager.UninstallPlugin(plugin!); + _installationManager.UninstallPlugin(plugin); return NoContent(); } From 3a8d395c9c88fb5a6e170504ee8d865d880d7ed6 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:23:46 +0000 Subject: [PATCH 36/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 975b708435..34e8219e15 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -27,7 +27,6 @@ namespace Emby.Server.Implementations private readonly JsonSerializerOptions _jsonOptions; private readonly ILogger _logger; private readonly IApplicationHost _appHost; - private readonly string _imagesPath; private readonly ServerConfiguration _config; private readonly IList _plugins; private readonly Version _nextVersion; From 2d094bedf5d8b8e0e33e5bc0f59f9f39cb7b40c7 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:24:00 +0000 Subject: [PATCH 37/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 34e8219e15..66a233fbe7 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -425,7 +425,7 @@ namespace Emby.Server.Implementations try { _logger.LogDebug("Creating instance of {Type}", type); - var instance = ActivatorUtilities.CreateInstance(_appHost.ServiceProvider, type); + var instance = (IPlugin)ActivatorUtilities.CreateInstance(_appHost.ServiceProvider, type); if (plugin == null) { // Create a dummy record for the providers. From 30645e72654e0d4918c86b610cbe9e8c2e4f4907 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:24:12 +0000 Subject: [PATCH 38/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 66a233fbe7..6eeb8613c6 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -429,7 +429,6 @@ namespace Emby.Server.Implementations if (plugin == null) { // Create a dummy record for the providers. - var pInstance = (IPlugin)instance; plugin = new LocalPlugin( pInstance.AssemblyFilePath, true, From 591ad3b04b7816d447ccd60c096a7fe730b3dea6 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:24:26 +0000 Subject: [PATCH 39/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 6eeb8613c6..e92b02191a 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -450,7 +450,7 @@ namespace Emby.Server.Implementations } else { - plugin.Instance = (IPlugin)instance; + plugin.Instance = instance; var manifest = plugin.Manifest; var pluginStr = plugin.Instance.Version.ToString(); bool changed = false; From 8ce765c4d1cd289c88673f79a8137bf2f5cbc241 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:25:04 +0000 Subject: [PATCH 40/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index e92b02191a..8c0cd9a36d 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -15,7 +15,7 @@ using MediaBrowser.Model.Plugins; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations +namespace Emby.Server.Implementations.Plugins { /// /// Defines the . From 0dcf6b09c1ad7913a27bf56b19716a0da957054e Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:25:39 +0000 Subject: [PATCH 41/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 8c0cd9a36d..874e88c574 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Plugins /// The image cache path. /// The application version. public PluginManager( - ILoggerFactory loggerfactory, + ILogger logger, IApplicationHost appHost, ServerConfiguration config, string pluginsPath, From 4153551dfcd498114d240b0311b20df8799c1593 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 08:26:11 +0000 Subject: [PATCH 42/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 874e88c574..2e4b23bcf8 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -413,7 +413,7 @@ namespace Emby.Server.Implementations.Plugins /// /// The type. /// System.Object. - private object? CreatePluginInstance(Type type) + private IPlugin? CreatePluginInstance(Type type) { // Find the record for this plugin. var plugin = GetPluginByType(type); From 5a3efc526631b7f774b17ea5a8a54130696b6e25 Mon Sep 17 00:00:00 2001 From: Greenback Date: Fri, 18 Dec 2020 09:04:40 +0000 Subject: [PATCH 43/77] Changes as required. --- .../ApplicationHost.cs | 3 +- .../Plugins/PluginManager.cs | 102 +++++++----------- MediaBrowser.Common/Plugins/BasePlugin.cs | 41 +------ 3 files changed, 42 insertions(+), 104 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 216cb5e758..d7bc83f3a5 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -35,6 +35,7 @@ using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Playlists; +using Emby.Server.Implementations.Plugins; using Emby.Server.Implementations.QuickConnect; using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; @@ -281,7 +282,7 @@ namespace Emby.Server.Implementations ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; _pluginManager = new PluginManager( - LoggerFactory, + LoggerFactory.CreateLogger(), this, ServerConfigurationManager.Configuration, ApplicationPaths.PluginsPath, diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 2e4b23bcf8..151e2c2036 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.Plugins /// /// Initializes a new instance of the class. /// - /// The . + /// The . /// The . /// The . /// The plugin path. @@ -49,13 +49,12 @@ namespace Emby.Server.Implementations.Plugins string imagesPath, Version appVersion) { - _logger = loggerfactory.CreateLogger(); + _logger = _logger ?? throw new ArgumentNullException(nameof(logger)); _pluginsPath = pluginsPath; _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); - _jsonOptions = JsonDefaults.GetCamelCaseOptions(); + _jsonOptions = JsonDefaults.GetOptions(); _config = config; _appHost = appHost; - _imagesPath = imagesPath; _nextVersion = new Version(_appVersion.Major, _appVersion.Minor + 2, _appVersion.Build, _appVersion.Revision); _minimumVersion = new Version(0, 0, 0, 1); _plugins = Directory.Exists(_pluginsPath) ? DiscoverPlugins().ToList() : new List(); @@ -273,62 +272,6 @@ namespace Emby.Server.Implementations.Plugins } } - private void CopyFiles(string source, string destination, bool overwrite, string searchPattern) - { - FileInfo[] files; - try - { - files = new DirectoryInfo(source).GetFiles(searchPattern); - } - catch (Exception ex) - { - _logger.LogDebug(ex, "Error retrieving file list."); - return; - } - - foreach (FileInfo file in files) - { - try - { - file.CopyTo(Path.Combine(destination, file.Name), overwrite); - } - catch (Exception ex) - { - _logger.LogDebug(ex, "Error copying file {Name}", file.Name); - } - } - } - - /// - /// Changes the status of the other versions of the plugin to "Superceded". - /// - /// The that's master. - private void UpdateSuccessors(LocalPlugin plugin) - { - // This value is memory only - so that the web will show restart required. - plugin.Manifest.Status = PluginStatus.Restart; - - // Detect whether there is another version of this plugin that needs disabling. - var predecessor = _plugins.OrderByDescending(p => p.Version) - .FirstOrDefault( - p => p.Id.Equals(plugin.Id) - && p.IsEnabledAndSupported - && p.Version != plugin.Version); - - if (predecessor == null) - { - return; - } - - // migrate settings across from the last active version if they don't exist. - CopyFiles(predecessor.Path, plugin.Path, false, "*.xml"); - - if (predecessor.Manifest.Status == PluginStatus.Active && !ChangePluginState(predecessor, PluginStatus.Superceded)) - { - _logger.LogError("Unable to disable version {Version} of {Name}", predecessor.Version, predecessor.Name); - } - } - /// /// Disable the plugin. /// @@ -429,19 +372,19 @@ namespace Emby.Server.Implementations.Plugins if (plugin == null) { // Create a dummy record for the providers. + // TODO: remove this code, if all provided have been released as separate plugins. plugin = new LocalPlugin( - pInstance.AssemblyFilePath, + instance.AssemblyFilePath, true, new PluginManifest { - Guid = pInstance.Id, + Guid = instance.Id, Status = PluginStatus.Active, - Name = pInstance.Name, - Version = pInstance.Version.ToString(), - MaxAbi = _nextVersion.ToString() + Name = instance.Name, + Version = instance.Version.ToString() }) { - Instance = pInstance + Instance = instance }; _plugins.Add(plugin); @@ -707,5 +650,32 @@ namespace Emby.Server.Implementations.Plugins // Only want plugin folders which have files. return versions.Where(p => p.DllFiles.Count != 0); } + + /// + /// Changes the status of the other versions of the plugin to "Superceded". + /// + /// The that's master. + private void UpdateSuccessors(LocalPlugin plugin) + { + // This value is memory only - so that the web will show restart required. + plugin.Manifest.Status = PluginStatus.Restart; + + // Detect whether there is another version of this plugin that needs disabling. + var predecessor = _plugins.OrderByDescending(p => p.Version) + .FirstOrDefault( + p => p.Id.Equals(plugin.Id) + && p.IsEnabledAndSupported + && p.Version != plugin.Version); + + if (predecessor == null) + { + return; + } + + if (predecessor.Manifest.Status == PluginStatus.Active && !ChangePluginState(predecessor, PluginStatus.Superceded)) + { + _logger.LogError("Unable to disable version {Version} of {Name}", predecessor.Version, predecessor.Name); + } + } } } diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 5756f8852c..5750c59c42 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -134,25 +134,11 @@ namespace MediaBrowser.Common.Plugins var assemblyName = assembly.GetName(); var assemblyFilePath = assembly.Location; - // Find out the plugin folder. - bool inPluginFolder = assemblyFilePath.StartsWith(ApplicationPaths.PluginsPath, StringComparison.OrdinalIgnoreCase); - string path, dataFolderPath; - - var configurationFileName = Path.ChangeExtension(Path.GetFileName(assemblyFilePath), ".xml"); - if (inPluginFolder) - { - // Normal plugin. - path = assemblyFilePath.Substring(ApplicationPaths.PluginsPath.Length).Split('\\', StringSplitOptions.RemoveEmptyEntries)[0]; - dataFolderPath = Path.Combine( - Path.Combine(ApplicationPaths.PluginsPath, path), - configurationFileName); - ConfigurationFilePath = dataFolderPath; - } - else + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + if (!Directory.Exists(dataFolderPath)) { - // Provider - dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - ConfigurationFilePath = Path.Combine(ApplicationPaths.PluginConfigurationsPath, configurationFileName); + // Try again with the version number appended to the folder name. + dataFolderPath = dataFolderPath + "_" + Version.ToString(); } assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); @@ -165,25 +151,6 @@ namespace MediaBrowser.Common.Plugins assemblyPlugin.SetId(assemblyId); } - - // TODO : Remove this, once migration support is ceased. - if (inPluginFolder) - { - var oldConfigFilePath = Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName); - - if (!File.Exists(ConfigurationFilePath) && File.Exists(oldConfigFilePath)) - { - // Migrate pre 10.7 settings, as different plugin versions may have different settings. - try - { - File.Copy(oldConfigFilePath, ConfigurationFilePath); - } - catch - { - // Unable to migrate settings. - } - } - } } if (this is IHasPluginConfiguration hasPluginConfiguration) From 486148dd6b18ec336ca076b8ec0a23d257789683 Mon Sep 17 00:00:00 2001 From: Greenback Date: Fri, 18 Dec 2020 09:44:57 +0000 Subject: [PATCH 44/77] Removed maxAbi --- .../ApplicationHost.cs | 1 - .../Plugins/PluginManager.cs | 11 +- .../Updates/InstallationManager.cs | 18 +- MediaBrowser.Common/Plugins/BasePlugin.cs | 210 ----------------- MediaBrowser.Common/Plugins/BasePluginOfT.cs | 218 ++++++++++++++++++ MediaBrowser.Model/Updates/VersionInfo.cs | 7 - 6 files changed, 224 insertions(+), 241 deletions(-) create mode 100644 MediaBrowser.Common/Plugins/BasePluginOfT.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index d7bc83f3a5..b91ba6b6ca 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -286,7 +286,6 @@ namespace Emby.Server.Implementations this, ServerConfigurationManager.Configuration, ApplicationPaths.PluginsPath, - ApplicationPaths.CachePath, ApplicationVersion); } diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 151e2c2036..4c508279c5 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -39,17 +39,15 @@ namespace Emby.Server.Implementations.Plugins /// The . /// The . /// The plugin path. - /// The image cache path. /// The application version. public PluginManager( ILogger logger, IApplicationHost appHost, ServerConfiguration config, string pluginsPath, - string imagesPath, Version appVersion) { - _logger = _logger ?? throw new ArgumentNullException(nameof(logger)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _pluginsPath = pluginsPath; _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); _jsonOptions = JsonDefaults.GetOptions(); @@ -509,17 +507,12 @@ namespace Emby.Server.Implementations.Plugins targetAbi = _minimumVersion; } - if (!Version.TryParse(manifest.MaxAbi, out var maxAbi)) - { - maxAbi = _appVersion; - } - if (!Version.TryParse(manifest.Version, out version)) { manifest.Version = _minimumVersion.ToString(); } - return new LocalPlugin(dir, _appVersion >= targetAbi && _appVersion <= maxAbi, manifest); + return new LocalPlugin(dir, _appVersion >= targetAbi, manifest); } // No metafile, so lets see if the folder is versioned. diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 70424369bd..cf059fb97f 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -132,13 +132,8 @@ namespace Emby.Server.Implementations.Updates targetAbi = minimumVersion; } - if (!Version.TryParse(ver.MaxAbi, out var maxAbi)) - { - maxAbi = _applicationHost.ApplicationVersion; - } - // Only show plugins that fall between targetAbi and maxAbi - if (_applicationHost.ApplicationVersion >= targetAbi && _applicationHost.ApplicationVersion <= maxAbi) + if (_applicationHost.ApplicationVersion >= targetAbi) { continue; } @@ -200,19 +195,15 @@ namespace Emby.Server.Implementations.Updates // Update the manifests, if anything changes. if (plugin != null) { - bool noChange = string.Equals(plugin.Manifest.MaxAbi, version.MaxAbi, StringComparison.Ordinal) - || string.Equals(plugin.Manifest.TargetAbi, version.TargetAbi, StringComparison.Ordinal); - if (!noChange) + if (!string.Equals(plugin.Manifest.TargetAbi, version.TargetAbi, StringComparison.Ordinal)) { - plugin.Manifest.MaxAbi = version.MaxAbi ?? string.Empty; plugin.Manifest.TargetAbi = version.TargetAbi ?? string.Empty; _pluginManager.SaveManifest(plugin.Manifest, plugin.Path); } } // Remove versions with a target abi that is greater then the current application version. - if ((Version.TryParse(version.TargetAbi, out var targetAbi) && _applicationHost.ApplicationVersion < targetAbi) - || (Version.TryParse(version.MaxAbi, out var maxAbi) && _applicationHost.ApplicationVersion > maxAbi)) + if (Version.TryParse(version.TargetAbi, out var targetAbi) && _applicationHost.ApplicationVersion < targetAbi) { package.Versions.RemoveAt(i); } @@ -283,8 +274,7 @@ namespace Emby.Server.Implementations.Updates var appVer = _applicationHost.ApplicationVersion; var availableVersions = package.Versions - .Where(x => (string.IsNullOrEmpty(x.TargetAbi) || Version.Parse(x.TargetAbi) <= appVer) - && (string.IsNullOrEmpty(x.MaxAbi) || Version.Parse(x.MaxAbi) >= appVer)); + .Where(x => string.IsNullOrEmpty(x.TargetAbi) || Version.Parse(x.TargetAbi) <= appVer); if (specificVersion != null) { diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 5750c59c42..e228ae7ec5 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -1,5 +1,3 @@ -#pragma warning disable SA1402 - using System; using System.IO; using System.Reflection; @@ -94,212 +92,4 @@ namespace MediaBrowser.Common.Plugins Id = assemblyId; } } - - /// - /// Provides a common base class for all plugins. - /// - /// The type of the T configuration type. - public abstract class BasePlugin : BasePlugin, IHasPluginConfiguration - where TConfigurationType : BasePluginConfiguration - { - /// - /// The configuration sync lock. - /// - private readonly object _configurationSyncLock = new object(); - - /// - /// The configuration save lock. - /// - private readonly object _configurationSaveLock = new object(); - - private Action _directoryCreateFn; - - /// - /// The configuration. - /// - private TConfigurationType _configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The application paths. - /// The XML serializer. - protected BasePlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) - { - ApplicationPaths = applicationPaths; - XmlSerializer = xmlSerializer; - if (this is IPluginAssembly assemblyPlugin) - { - var assembly = GetType().Assembly; - var assemblyName = assembly.GetName(); - var assemblyFilePath = assembly.Location; - - var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - if (!Directory.Exists(dataFolderPath)) - { - // Try again with the version number appended to the folder name. - dataFolderPath = dataFolderPath + "_" + Version.ToString(); - } - - assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); - - var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); - if (idAttributes.Length > 0) - { - var attribute = (GuidAttribute)idAttributes[0]; - var assemblyId = new Guid(attribute.Value); - - assemblyPlugin.SetId(assemblyId); - } - } - - if (this is IHasPluginConfiguration hasPluginConfiguration) - { - hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); - } - } - - /// - /// Gets the application paths. - /// - /// The application paths. - protected IApplicationPaths ApplicationPaths { get; private set; } - - /// - /// Gets the XML serializer. - /// - /// The XML serializer. - protected IXmlSerializer XmlSerializer { get; private set; } - - /// - /// Gets the type of configuration this plugin uses. - /// - /// The type of the configuration. - public Type ConfigurationType => typeof(TConfigurationType); - - /// - /// Gets or sets the event handler that is triggered when this configuration changes. - /// - public EventHandler ConfigurationChanged { get; set; } - - /// - /// Gets the name the assembly file. - /// - /// The name of the assembly file. - protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath); - - /// - /// Gets or sets the plugin configuration. - /// - /// The configuration. - public TConfigurationType Configuration - { - get - { - // Lazy load - if (_configuration == null) - { - lock (_configurationSyncLock) - { - if (_configuration == null) - { - _configuration = LoadConfiguration(); - } - } - } - - return _configuration; - } - - protected set => _configuration = value; - } - - /// - /// Gets the name of the configuration file. Subclasses should override. - /// - /// The name of the configuration file. - public virtual string ConfigurationFileName => Path.ChangeExtension(AssemblyFileName, ".xml"); - - /// - /// Gets the full path to the configuration file. - /// - /// The configuration file path. - public string ConfigurationFilePath { get; } - - /// - /// Gets the plugin configuration. - /// - /// The configuration. - BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration; - - /// - public void SetStartupInfo(Action directoryCreateFn) - { - // hack alert, until the .net core transition is complete - _directoryCreateFn = directoryCreateFn; - } - - private TConfigurationType LoadConfiguration() - { - var path = ConfigurationFilePath; - - try - { - return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path); - } - catch - { - var config = (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType)); - SaveConfiguration(config); - return config; - } - } - - /// - /// Saves the current configuration to the file system. - /// - /// Configuration to save. - public virtual void SaveConfiguration(TConfigurationType config) - { - lock (_configurationSaveLock) - { - _directoryCreateFn(Path.GetDirectoryName(ConfigurationFilePath)); - - XmlSerializer.SerializeToFile(config, ConfigurationFilePath); - } - } - - /// - /// Saves the current configuration to the file system. - /// - public virtual void SaveConfiguration() - { - SaveConfiguration(Configuration); - } - - /// - public virtual void UpdateConfiguration(BasePluginConfiguration configuration) - { - if (configuration == null) - { - throw new ArgumentNullException(nameof(configuration)); - } - - Configuration = (TConfigurationType)configuration; - - SaveConfiguration(Configuration); - - ConfigurationChanged?.Invoke(this, configuration); - } - - /// - public override PluginInfo GetPluginInfo() - { - var info = base.GetPluginInfo(); - - info.ConfigurationFileName = ConfigurationFileName; - - return info; - } - } } diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs new file mode 100644 index 0000000000..66aec92ab6 --- /dev/null +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -0,0 +1,218 @@ +#pragma warning disable SA1649 // File name should match first type name +using System; +using System.IO; +using System.Runtime.InteropServices; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Provides a common base class for all plugins. + /// + /// The type of the T configuration type. + public abstract class BasePlugin : BasePlugin, IHasPluginConfiguration + where TConfigurationType : BasePluginConfiguration + { + /// + /// The configuration sync lock. + /// + private readonly object _configurationSyncLock = new object(); + + /// + /// The configuration save lock. + /// + private readonly object _configurationSaveLock = new object(); + + private Action _directoryCreateFn; + + /// + /// The configuration. + /// + private TConfigurationType _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The XML serializer. + protected BasePlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + { + ApplicationPaths = applicationPaths; + XmlSerializer = xmlSerializer; + if (this is IPluginAssembly assemblyPlugin) + { + var assembly = GetType().Assembly; + var assemblyName = assembly.GetName(); + var assemblyFilePath = assembly.Location; + + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + if (!Directory.Exists(dataFolderPath)) + { + // Try again with the version number appended to the folder name. + dataFolderPath = dataFolderPath + "_" + Version.ToString(); + } + + assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); + + var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); + if (idAttributes.Length > 0) + { + var attribute = (GuidAttribute)idAttributes[0]; + var assemblyId = new Guid(attribute.Value); + + assemblyPlugin.SetId(assemblyId); + } + } + + if (this is IHasPluginConfiguration hasPluginConfiguration) + { + hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); + } + } + + /// + /// Gets the application paths. + /// + /// The application paths. + protected IApplicationPaths ApplicationPaths { get; private set; } + + /// + /// Gets the XML serializer. + /// + /// The XML serializer. + protected IXmlSerializer XmlSerializer { get; private set; } + + /// + /// Gets the type of configuration this plugin uses. + /// + /// The type of the configuration. + public Type ConfigurationType => typeof(TConfigurationType); + + /// + /// Gets or sets the event handler that is triggered when this configuration changes. + /// + public EventHandler ConfigurationChanged { get; set; } + + /// + /// Gets the name the assembly file. + /// + /// The name of the assembly file. + protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath); + + /// + /// Gets or sets the plugin configuration. + /// + /// The configuration. + public TConfigurationType Configuration + { + get + { + // Lazy load + if (_configuration == null) + { + lock (_configurationSyncLock) + { + if (_configuration == null) + { + _configuration = LoadConfiguration(); + } + } + } + + return _configuration; + } + + protected set => _configuration = value; + } + + /// + /// Gets the name of the configuration file. Subclasses should override. + /// + /// The name of the configuration file. + public virtual string ConfigurationFileName => Path.ChangeExtension(AssemblyFileName, ".xml"); + + /// + /// Gets the full path to the configuration file. + /// + /// The configuration file path. + public string ConfigurationFilePath { get; } + + /// + /// Gets the plugin configuration. + /// + /// The configuration. + BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration; + + /// + public void SetStartupInfo(Action directoryCreateFn) + { + // hack alert, until the .net core transition is complete + _directoryCreateFn = directoryCreateFn; + } + + /// + /// Saves the current configuration to the file system. + /// + /// Configuration to save. + public virtual void SaveConfiguration(TConfigurationType config) + { + lock (_configurationSaveLock) + { + _directoryCreateFn(Path.GetDirectoryName(ConfigurationFilePath)); + + XmlSerializer.SerializeToFile(config, ConfigurationFilePath); + } + } + + /// + /// Saves the current configuration to the file system. + /// + public virtual void SaveConfiguration() + { + SaveConfiguration(Configuration); + } + + /// + public virtual void UpdateConfiguration(BasePluginConfiguration configuration) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + Configuration = (TConfigurationType)configuration; + + SaveConfiguration(Configuration); + + ConfigurationChanged?.Invoke(this, configuration); + } + + /// + public override PluginInfo GetPluginInfo() + { + var info = base.GetPluginInfo(); + + info.ConfigurationFileName = ConfigurationFileName; + + return info; + } + + private TConfigurationType LoadConfiguration() + { + var path = ConfigurationFilePath; + + try + { + return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path); + } + catch + { + var config = (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType)); + SaveConfiguration(config); + return config; + } + } + } +} diff --git a/MediaBrowser.Model/Updates/VersionInfo.cs b/MediaBrowser.Model/Updates/VersionInfo.cs index 503dba0a14..209092265e 100644 --- a/MediaBrowser.Model/Updates/VersionInfo.cs +++ b/MediaBrowser.Model/Updates/VersionInfo.cs @@ -43,13 +43,6 @@ namespace MediaBrowser.Model.Updates [JsonPropertyName("targetAbi")] public string? TargetAbi { get; set; } - /// - /// Gets or sets the maximum ABI that this version will work with. - /// - /// The target ABI version. - [JsonPropertyName("maxAbi")] - public string? MaxAbi { get; set; } - /// /// Gets or sets the source URL. /// From ce19f2be55c7484488bebfd29bd42c1f082ae888 Mon Sep 17 00:00:00 2001 From: Greenback Date: Fri, 18 Dec 2020 20:37:35 +0000 Subject: [PATCH 45/77] Renamed Guid property to Id --- .../Plugins/PluginManager.cs | 4 +-- .../Updates/InstallationManager.cs | 12 +++---- MediaBrowser.Common/Plugins/BasePluginOfT.cs | 17 ++++++++-- MediaBrowser.Common/Plugins/LocalPlugin.cs | 6 ++-- MediaBrowser.Common/Plugins/PluginManifest.cs | 6 ++-- MediaBrowser.Model/Plugins/PluginPageInfo.cs | 34 +++++++++++++++---- .../Updates/InstallationInfo.cs | 8 +++-- MediaBrowser.Model/Updates/PackageInfo.cs | 6 ++-- .../Plugins/AudioDb/Plugin.cs | 2 +- .../Plugins/MusicBrainz/Plugin.cs | 2 +- MediaBrowser.Providers/Plugins/Omdb/Plugin.cs | 2 +- 11 files changed, 65 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 4c508279c5..613e610d3f 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -376,7 +376,7 @@ namespace Emby.Server.Implementations.Plugins true, new PluginManifest { - Guid = instance.Id, + Id = instance.Id, Status = PluginStatus.Active, Name = instance.Name, Version = instance.Version.ToString() @@ -537,7 +537,7 @@ namespace Emby.Server.Implementations.Plugins Status = PluginStatus.Restart, Name = metafile, AutoUpdate = false, - Guid = metafile.GetMD5(), + Id = metafile.GetMD5(), TargetAbi = _appVersion.ToString(), Version = version.ToString() }; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index cf059fb97f..267a338750 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.Updates // Where repositories have the same content, the details from the first is taken. foreach (var package in await GetPackages(repository.Name ?? "Unnamed Repo", repository.Url, true, cancellationToken).ConfigureAwait(true)) { - if (!Guid.TryParse(package.Guid, out var packageGuid)) + if (!Guid.TryParse(package.Id, out var packageGuid)) { // Package doesn't have a valid GUID, skip. continue; @@ -245,7 +245,7 @@ namespace Emby.Server.Implementations.Updates if (guid != Guid.Empty) { - availablePackages = availablePackages.Where(x => Guid.Parse(x.Guid) == guid); + availablePackages = availablePackages.Where(x => Guid.Parse(x.Id) == guid); } if (specificVersion != null) @@ -290,7 +290,7 @@ namespace Emby.Server.Implementations.Updates yield return new InstallationInfo { Changelog = v.Changelog, - Guid = new Guid(package.Guid), + Id = new Guid(package.Id), Name = package.Name, Version = v.VersionNumber, SourceUrl = v.SourceUrl, @@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.Updates { lock (_currentInstallationsLock) { - var install = _currentInstallations.Find(x => x.info.Guid == id); + var install = _currentInstallations.Find(x => x.info.Id == id); if (install == default((InstallationInfo, CancellationTokenSource))) { return false; @@ -512,7 +512,7 @@ namespace Emby.Server.Implementations.Updates var compatibleVersions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, minVersion: plugin.Version); var version = compatibleVersions.FirstOrDefault(y => y.Version > plugin.Version); - if (version != null && CompletedInstallations.All(x => x.Guid != version.Guid)) + if (version != null && CompletedInstallations.All(x => x.Id != version.Id)) { yield return version; } @@ -577,7 +577,7 @@ namespace Emby.Server.Implementations.Updates private async Task InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken) { // Set last update time if we were installed before - LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Guid) && p.Version.Equals(package.Version)) + LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version)) ?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version)); if (plugin != null) { diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index 66aec92ab6..e4e766472c 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Common.Plugins var assemblyFilePath = assembly.Location; var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - if (!Directory.Exists(dataFolderPath)) + if (!Directory.Exists(dataFolderPath) && Version != null) { // Try again with the version number appended to the folder name. dataFolderPath = dataFolderPath + "_" + Version.ToString(); @@ -137,7 +137,20 @@ namespace MediaBrowser.Common.Plugins /// Gets the full path to the configuration file. /// /// The configuration file path. - public string ConfigurationFilePath { get; } + public string ConfigurationFilePath + { + get + { + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(AssemblyFilePath)); + if (!Directory.Exists(dataFolderPath) && Version != null) + { + // Try again with the version number appended to the folder name. + return dataFolderPath + "_" + Version.ToString(); + } + + return dataFolderPath; + } + } /// /// Gets the plugin configuration. diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index 8aded8b9bc..40ecb0a67b 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -1,8 +1,6 @@ #nullable enable using System; using System.Collections.Generic; -using System.Globalization; -using System.Reflection; using MediaBrowser.Model.Plugins; namespace MediaBrowser.Common.Plugins @@ -32,7 +30,7 @@ namespace MediaBrowser.Common.Plugins /// /// Gets the plugin id. /// - public Guid Id => Manifest.Guid; + public Guid Id => Manifest.Id; /// /// Gets the plugin name. @@ -110,7 +108,7 @@ namespace MediaBrowser.Common.Plugins /// A instance containing the information. public PluginInfo GetPluginInfo() { - var inst = Instance?.GetPluginInfo() ?? new PluginInfo(Manifest.Name, Version, Manifest.Description, Manifest.Guid, true); + var inst = Instance?.GetPluginInfo() ?? new PluginInfo(Manifest.Name, Version, Manifest.Description, Manifest.Id, true); inst.Status = Manifest.Status; inst.HasImage = !string.IsNullOrEmpty(Manifest.ImageUrl); return inst; diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index 1bd17933c4..39ee450a6e 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Text.Json.Serialization; using MediaBrowser.Model.Plugins; namespace MediaBrowser.Common.Plugins @@ -27,9 +28,8 @@ namespace MediaBrowser.Common.Plugins /// /// Gets or sets the Global Unique Identifier for the plugin. /// -#pragma warning disable CA1720 // Identifier contains type name - public Guid Guid { get; set; } -#pragma warning restore CA1720 // Identifier contains type name + [JsonPropertyName("Guid")] + public Guid Id { get; set; } /// /// Gets or sets the Name of the plugin. diff --git a/MediaBrowser.Model/Plugins/PluginPageInfo.cs b/MediaBrowser.Model/Plugins/PluginPageInfo.cs index ca72e19ee1..85c0aa204b 100644 --- a/MediaBrowser.Model/Plugins/PluginPageInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginPageInfo.cs @@ -1,20 +1,40 @@ -#nullable disable -#pragma warning disable CS1591 +#nullable enable namespace MediaBrowser.Model.Plugins { + /// + /// Defines the . + /// public class PluginPageInfo { - public string Name { get; set; } + /// + /// Gets or sets the name. + /// + public string Name { get; set; } = string.Empty; - public string DisplayName { get; set; } + /// + /// Gets or sets the display name. + /// + public string? DisplayName { get; set; } - public string EmbeddedResourcePath { get; set; } + /// + /// Gets or sets the resource path. + /// + public string EmbeddedResourcePath { get; set; } = string.Empty; + /// + /// Gets or sets a value indicating whether this plugin should appear in the main menu. + /// public bool EnableInMainMenu { get; set; } - public string MenuSection { get; set; } + /// + /// Gets or sets the menu section. + /// + public string? MenuSection { get; set; } - public string MenuIcon { get; set; } + /// + /// Gets or sets the menu icon. + /// + public string? MenuIcon { get; set; } } } diff --git a/MediaBrowser.Model/Updates/InstallationInfo.cs b/MediaBrowser.Model/Updates/InstallationInfo.cs index a6d80dba62..eebe1a9034 100644 --- a/MediaBrowser.Model/Updates/InstallationInfo.cs +++ b/MediaBrowser.Model/Updates/InstallationInfo.cs @@ -1,5 +1,6 @@ #nullable disable using System; +using System.Text.Json.Serialization; namespace MediaBrowser.Model.Updates { @@ -9,10 +10,11 @@ namespace MediaBrowser.Model.Updates public class InstallationInfo { /// - /// Gets or sets the guid. + /// Gets or sets the Id. /// - /// The guid. - public Guid Guid { get; set; } + /// The Id. + [JsonPropertyName("Guid")] + public Guid Id { get; set; } /// /// Gets or sets the name. diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index 63fd717429..2d05ed636a 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Updates public PackageInfo() { Versions = Array.Empty(); - Guid = string.Empty; + Id = string.Empty; Category = string.Empty; Name = string.Empty; Overview = string.Empty; @@ -65,9 +65,7 @@ namespace MediaBrowser.Model.Updates /// /// The name. [JsonPropertyName("guid")] -#pragma warning disable CA1720 // Identifier contains type name - public string Guid { get; set; } -#pragma warning restore CA1720 // Identifier contains type name + public string Id { get; set; } /// /// Gets or sets the versions. diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs index b5bd72ff0d..ba0d7b5697 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using System; using System.Collections.Generic; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs index 90266e4409..43bd3a472f 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using System; using System.Collections.Generic; diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs index 41ca561643..d7f6781e50 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using System; using System.Collections.Generic; From 5d748c0e9f1ad87bece090934c5da6c4edacb8f7 Mon Sep 17 00:00:00 2001 From: Greenback Date: Fri, 18 Dec 2020 20:52:44 +0000 Subject: [PATCH 46/77] Renamed to ImagePath --- Jellyfin.Api/Controllers/PluginsController.cs | 10 +++++----- MediaBrowser.Common/Plugins/LocalPlugin.cs | 2 +- MediaBrowser.Common/Plugins/PluginManifest.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 1365764fba..365bb2248c 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -303,16 +303,16 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - var imgPath = Path.Combine(plugin.Path, plugin.Manifest.ImageUrl ?? string.Empty); + var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty); if (((ServerConfiguration)_config.CommonConfiguration).DisablePluginImages - || plugin.Manifest.ImageUrl == null - || !System.IO.File.Exists(imgPath)) + || plugin.Manifest.ImagePath == null + || !System.IO.File.Exists(imagePath)) { return NotFound(); } - imgPath = Path.Combine(plugin.Path, plugin.Manifest.ImageUrl); - return PhysicalFile(imgPath, MimeTypes.GetMimeType(imgPath)); + imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath); + return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath)); } /// diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index 40ecb0a67b..23b6cfa81a 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -110,7 +110,7 @@ namespace MediaBrowser.Common.Plugins { var inst = Instance?.GetPluginInfo() ?? new PluginInfo(Manifest.Name, Version, Manifest.Description, Manifest.Id, true); inst.Status = Manifest.Status; - inst.HasImage = !string.IsNullOrEmpty(Manifest.ImageUrl); + inst.HasImage = !string.IsNullOrEmpty(Manifest.ImagePath); return inst; } diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index 39ee450a6e..755ccafda1 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -80,6 +80,6 @@ namespace MediaBrowser.Common.Plugins /// Gets or sets a value indicating whether this plugin has an image. /// Image must be located in the local plugin folder. /// - public string? ImageUrl { get; set; } + public string? ImagePath { get; set; } } } From 5c4fdaa2530cd1b0b9dc88352d92c546ce176430 Mon Sep 17 00:00:00 2001 From: Greenback Date: Fri, 18 Dec 2020 21:05:27 +0000 Subject: [PATCH 47/77] MaxAbi property removed. --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- MediaBrowser.Common/Plugins/PluginManifest.cs | 5 ----- MediaBrowser.Model/Plugins/PluginStatus.cs | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 267a338750..630ede667c 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Updates targetAbi = minimumVersion; } - // Only show plugins that fall between targetAbi and maxAbi + // Only show plugins that fall between targetAbi. if (_applicationHost.ApplicationVersion >= targetAbi) { continue; diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index 755ccafda1..9c45e5d950 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -51,11 +51,6 @@ namespace MediaBrowser.Common.Plugins /// public string TargetAbi { get; set; } = string.Empty; - /// - /// Gets or sets the upper compatibility version for the plugin. - /// - public string MaxAbi { get; set; } = string.Empty; - /// /// Gets or sets the timestamp of the plugin. /// diff --git a/MediaBrowser.Model/Plugins/PluginStatus.cs b/MediaBrowser.Model/Plugins/PluginStatus.cs index 3ea05174fd..4b9b9bbeee 100644 --- a/MediaBrowser.Model/Plugins/PluginStatus.cs +++ b/MediaBrowser.Model/Plugins/PluginStatus.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Plugins Disabled = -1, /// - /// This plugin does not meet the TargetAbi / MaxAbi requirements. + /// This plugin does not meet the TargetAbi requirements. /// NotSupported = -2, From 46a64deb66a01ad95481147de977796e01e73329 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 21:07:24 +0000 Subject: [PATCH 48/77] Update MediaBrowser.Model/Updates/PackageInfo.cs Co-authored-by: Cody Robibero --- MediaBrowser.Model/Updates/PackageInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index 2d05ed636a..8f7d7ede6a 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Model.Updates /// /// Gets or sets the image url for the package. /// - [JsonPropertyName("imageUrl")] - public string? ImageUrl { get; set; } + [JsonPropertyName("imagePath")] + public string? ImagePath { get; set; } } } From cb793af30e5785f2063e98802cfa65917cde0a46 Mon Sep 17 00:00:00 2001 From: Greenback Date: Fri, 18 Dec 2020 21:11:29 +0000 Subject: [PATCH 49/77] Renamed guid to id --- .../Updates/InstallationManager.cs | 10 +++++----- MediaBrowser.Common/Updates/IInstallationManager.cs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 630ede667c..fa62c08589 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -235,7 +235,7 @@ namespace Emby.Server.Implementations.Updates public IEnumerable FilterPackages( IEnumerable availablePackages, string? name = null, - Guid? guid = default, + Guid? id = default, Version? specificVersion = null) { if (name != null) @@ -243,9 +243,9 @@ namespace Emby.Server.Implementations.Updates availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); } - if (guid != Guid.Empty) + if (id != Guid.Empty) { - availablePackages = availablePackages.Where(x => Guid.Parse(x.Id) == guid); + availablePackages = availablePackages.Where(x => Guid.Parse(x.Id) == id); } if (specificVersion != null) @@ -260,11 +260,11 @@ namespace Emby.Server.Implementations.Updates public IEnumerable GetCompatibleVersions( IEnumerable availablePackages, string? name = null, - Guid? guid = default, + Guid? id = default, Version? minVersion = null, Version? specificVersion = null) { - var package = FilterPackages(availablePackages, name, guid, specificVersion).FirstOrDefault(); + var package = FilterPackages(availablePackages, name, id, specificVersion).FirstOrDefault(); // Package not found in repository if (package == null) diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index dd9e0cc3f9..4c8a6e959a 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -41,14 +41,14 @@ namespace MediaBrowser.Common.Updates /// /// The available packages. /// The name of the plugin. - /// The id of the plugin. + /// The id of the plugin. /// The version of the plugin. /// All plugins matching the requirements. IEnumerable FilterPackages( IEnumerable availablePackages, string? name = null, #pragma warning disable CA1720 // Identifier contains type name - Guid? guid = default, + Guid? id = default, #pragma warning restore CA1720 // Identifier contains type name Version? specificVersion = null); @@ -57,7 +57,7 @@ namespace MediaBrowser.Common.Updates /// /// The available packages. /// The name. - /// The guid of the plugin. + /// The id of the plugin. /// The minimum required version of the plugin. /// The specific version of the plugin to install. /// All compatible versions ordered from newest to oldest. @@ -65,7 +65,7 @@ namespace MediaBrowser.Common.Updates IEnumerable availablePackages, string? name = null, #pragma warning disable CA1720 // Identifier contains type name - Guid? guid = default, + Guid? id = default, #pragma warning restore CA1720 // Identifier contains type name Version? minVersion = null, Version? specificVersion = null); From 4cc19be173baab9bf93a310db6790d9c33e8ad17 Mon Sep 17 00:00:00 2001 From: Greenback Date: Fri, 18 Dec 2020 21:14:38 +0000 Subject: [PATCH 50/77] removed compiler directives. --- MediaBrowser.Common/Updates/IInstallationManager.cs | 4 ---- MediaBrowser.Model/Updates/PackageInfo.cs | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 4c8a6e959a..0844c2d792 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -47,9 +47,7 @@ namespace MediaBrowser.Common.Updates IEnumerable FilterPackages( IEnumerable availablePackages, string? name = null, -#pragma warning disable CA1720 // Identifier contains type name Guid? id = default, -#pragma warning restore CA1720 // Identifier contains type name Version? specificVersion = null); /// @@ -64,9 +62,7 @@ namespace MediaBrowser.Common.Updates IEnumerable GetCompatibleVersions( IEnumerable availablePackages, string? name = null, -#pragma warning disable CA1720 // Identifier contains type name Guid? id = default, -#pragma warning restore CA1720 // Identifier contains type name Version? minVersion = null, Version? specificVersion = null); diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index 8f7d7ede6a..0902e04409 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -77,7 +77,7 @@ namespace MediaBrowser.Model.Updates #pragma warning restore CA2227 // Collection properties should be read only /// - /// Gets or sets the image url for the package. + /// Gets or sets the image path for the package. /// [JsonPropertyName("imagePath")] public string? ImagePath { get; set; } From 4757824a826b05b281a83945ef39d19fa04981e4 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 21:55:50 +0000 Subject: [PATCH 51/77] Update Emby.Server.Implementations/Updates/InstallationManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index fa62c08589..4c2424c15b 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Updates targetAbi = minimumVersion; } - // Only show plugins that fall between targetAbi. + // Only show plugins that are greater than or equal to targetAbi. if (_applicationHost.ApplicationVersion >= targetAbi) { continue; From 3708ca8dbb0eff2eaa6c39b22cc1a2fd6e197dff Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 21:56:03 +0000 Subject: [PATCH 52/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 613e610d3f..6ed9623f99 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -29,7 +29,6 @@ namespace Emby.Server.Implementations.Plugins private readonly IApplicationHost _appHost; private readonly ServerConfiguration _config; private readonly IList _plugins; - private readonly Version _nextVersion; private readonly Version _minimumVersion; /// From 09f219bbcee78569537bb417de27f842879f2233 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 21:56:15 +0000 Subject: [PATCH 53/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 6ed9623f99..dc2eca4a1c 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -52,7 +52,6 @@ namespace Emby.Server.Implementations.Plugins _jsonOptions = JsonDefaults.GetOptions(); _config = config; _appHost = appHost; - _nextVersion = new Version(_appVersion.Major, _appVersion.Minor + 2, _appVersion.Build, _appVersion.Revision); _minimumVersion = new Version(0, 0, 0, 1); _plugins = Directory.Exists(_pluginsPath) ? DiscoverPlugins().ToList() : new List(); } From 5c3ebb63e62948ce9d405f7cbb82d4579e568833 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 21:56:35 +0000 Subject: [PATCH 54/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index dc2eca4a1c..be08d8515e 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -113,7 +113,7 @@ namespace Emby.Server.Implementations.Plugins /// public void CreatePlugins() { - var createdPlugins = _appHost.GetExports(CreatePluginInstance) + _ = _appHost.GetExports(CreatePluginInstance) .Where(i => i != null) .ToArray(); } From a293024efd5ebf8d04b53de0999da78a64a857e5 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 21:56:54 +0000 Subject: [PATCH 55/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index be08d8515e..1fd87273f7 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Plugins throw new ArgumentNullException(nameof(assembly)); } - var plugin = _plugins.Where(p => p.DllFiles.Contains(assembly.Location)).FirstOrDefault(); + var plugin = _plugins.FirstOrDefault(p => p.DllFiles.Contains(assembly.Location)); if (plugin == null) { // A plugin's assembly didn't cause this issue, so ignore it. From 9bf970e5c6d67a7e55c9faceda042e45ad06b532 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 21:59:14 +0000 Subject: [PATCH 56/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Plugins/PluginManager.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 1fd87273f7..d7292f21ba 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -629,11 +629,7 @@ namespace Emby.Server.Implementations.Plugins continue; } - if (manifest.Status != PluginStatus.Deleted) - { - manifest.Status = PluginStatus.Deleted; - SaveManifest(manifest, entry.Path); - } + ChangePluginState(entry, PluginStatus.Deleted); } } } From d34428f2f779dcd033f6cd060dc773515d2fbc6a Mon Sep 17 00:00:00 2001 From: Greenback Date: Fri, 18 Dec 2020 22:17:46 +0000 Subject: [PATCH 57/77] removed exception --- .../Plugins/PluginManager.cs | 120 ++++++++---------- 1 file changed, 52 insertions(+), 68 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index d7292f21ba..32646d1bf2 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Plugins // Now load the assemblies.. foreach (var plugin in _plugins) { - CheckIfStillSuperceded(plugin); + UpdatePluginSuperceedStatus(plugin); if (plugin.IsEnabledAndSupported == false) { @@ -134,7 +134,7 @@ namespace Emby.Server.Implementations.Plugins continue; } - CheckIfStillSuperceded(plugin); + UpdatePluginSuperceedStatus(plugin); if (!plugin.IsEnabledAndSupported) { continue; @@ -172,7 +172,7 @@ namespace Emby.Server.Implementations.Plugins // Load the plugin. var plugin = LoadManifest(folder); // Make sure we haven't already loaded this. - if (plugin == null || _plugins.Any(p => p.Manifest.Equals(plugin.Manifest))) + if (_plugins.Any(p => p.Manifest.Equals(plugin.Manifest))) { return; } @@ -435,7 +435,7 @@ namespace Emby.Server.Implementations.Plugins } } - private void CheckIfStillSuperceded(LocalPlugin plugin) + private void UpdatePluginSuperceedStatus(LocalPlugin plugin) { if (plugin.Manifest.Status != PluginStatus.Superceded) { @@ -464,7 +464,6 @@ namespace Emby.Server.Implementations.Plugins { Directory.Delete(plugin.Path, true); _logger.LogDebug("Deleted {Path}", plugin.Path); - _plugins.Remove(plugin); } #pragma warning disable CA1031 // Do not catch general exception types catch @@ -476,79 +475,69 @@ namespace Emby.Server.Implementations.Plugins return _plugins.Remove(plugin); } - private LocalPlugin? LoadManifest(string dir) + private LocalPlugin LoadManifest(string dir) { - try + Version? version; + PluginManifest? manifest = null; + var metafile = Path.Combine(dir, "meta.json"); + if (File.Exists(metafile)) { - Version? version; - PluginManifest? manifest = null; - var metafile = Path.Combine(dir, "meta.json"); - if (File.Exists(metafile)) + try { - try - { - var data = File.ReadAllText(metafile, Encoding.UTF8); - manifest = JsonSerializer.Deserialize(data, _jsonOptions); - } + var data = File.ReadAllText(metafile, Encoding.UTF8); + manifest = JsonSerializer.Deserialize(data, _jsonOptions); + } #pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) + catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types - { - _logger.LogError(ex, "Error deserializing {Path}.", dir); - } - } - - if (manifest != null) { - if (!Version.TryParse(manifest.TargetAbi, out var targetAbi)) - { - targetAbi = _minimumVersion; - } - - if (!Version.TryParse(manifest.Version, out version)) - { - manifest.Version = _minimumVersion.ToString(); - } - - return new LocalPlugin(dir, _appVersion >= targetAbi, manifest); + _logger.LogError(ex, "Error deserializing {Path}.", dir); } + } - // No metafile, so lets see if the folder is versioned. - // TODO: Phase this support out in future versions. - metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1]; - int versionIndex = dir.LastIndexOf('_'); - if (versionIndex != -1) + if (manifest != null) + { + if (!Version.TryParse(manifest.TargetAbi, out var targetAbi)) { - // Get the version number from the filename if possible. - metafile = Path.GetFileName(dir[..versionIndex]) ?? dir[..versionIndex]; - version = Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version? parsedVersion) ? parsedVersion : _appVersion; + targetAbi = _minimumVersion; } - else + + if (!Version.TryParse(manifest.Version, out version)) { - // Un-versioned folder - Add it under the path name and version it suitable for this instance. - version = _appVersion; + manifest.Version = _minimumVersion.ToString(); } - // Auto-create a plugin manifest, so we can disable it, if it fails to load. - manifest = new PluginManifest - { - Status = PluginStatus.Restart, - Name = metafile, - AutoUpdate = false, - Id = metafile.GetMD5(), - TargetAbi = _appVersion.ToString(), - Version = version.ToString() - }; + return new LocalPlugin(dir, _appVersion >= targetAbi, manifest); + } - return new LocalPlugin(dir, true, manifest); + // No metafile, so lets see if the folder is versioned. + // TODO: Phase this support out in future versions. + metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1]; + int versionIndex = dir.LastIndexOf('_'); + if (versionIndex != -1) + { + // Get the version number from the filename if possible. + metafile = Path.GetFileName(dir[..versionIndex]) ?? dir[..versionIndex]; + version = Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version? parsedVersion) ? parsedVersion : _appVersion; } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types + else { - _logger.LogError(ex, "Something went wrong!"); - return null; + // Un-versioned folder - Add it under the path name and version it suitable for this instance. + version = _appVersion; } + + // Auto-create a plugin manifest, so we can disable it, if it fails to load. + manifest = new PluginManifest + { + Status = PluginStatus.Restart, + Name = metafile, + AutoUpdate = false, + Id = metafile.GetMD5(), + TargetAbi = _appVersion.ToString(), + Version = version.ToString() + }; + + return new LocalPlugin(dir, true, manifest); } /// @@ -566,14 +555,9 @@ namespace Emby.Server.Implementations.Plugins } var directories = Directory.EnumerateDirectories(_pluginsPath, "*.*", SearchOption.TopDirectoryOnly); - LocalPlugin? entry; foreach (var dir in directories) { - entry = LoadManifest(dir); - if (entry != null) - { - versions.Add(entry); - } + versions.Add(LoadManifest(dir)); } string lastName = string.Empty; @@ -582,7 +566,7 @@ namespace Emby.Server.Implementations.Plugins // The first item will be the latest version. for (int x = versions.Count - 1; x >= 0; x--) { - entry = versions[x]; + var entry = versions[x]; if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase)) { entry.DllFiles.AddRange(Directory.EnumerateFiles(entry.Path, "*.dll", SearchOption.AllDirectories)); From bae8f0c4ec4a123db573579d09bf9051e84d3911 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 23:52:19 +0000 Subject: [PATCH 58/77] corrected. --- MediaBrowser.Common/Plugins/BasePluginOfT.cs | 25 ++++++-------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index e4e766472c..24a6dbeacc 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -15,6 +15,8 @@ namespace MediaBrowser.Common.Plugins public abstract class BasePlugin : BasePlugin, IHasPluginConfiguration where TConfigurationType : BasePluginConfiguration { + private readonly string _dataFolderPath; + /// /// The configuration sync lock. /// @@ -47,14 +49,14 @@ namespace MediaBrowser.Common.Plugins var assemblyName = assembly.GetName(); var assemblyFilePath = assembly.Location; - var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - if (!Directory.Exists(dataFolderPath) && Version != null) + _dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + if (!Directory.Exists(_dataFolderPath) && Version != null) { // Try again with the version number appended to the folder name. - dataFolderPath = dataFolderPath + "_" + Version.ToString(); + _dataFolderPath = _dataFolderPath + "_" + Version.ToString(); } - assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); + assemblyPlugin.SetAttributes(assemblyFilePath, _dataFolderPath, assemblyName.Version); var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); if (idAttributes.Length > 0) @@ -137,20 +139,7 @@ namespace MediaBrowser.Common.Plugins /// Gets the full path to the configuration file. /// /// The configuration file path. - public string ConfigurationFilePath - { - get - { - var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(AssemblyFilePath)); - if (!Directory.Exists(dataFolderPath) && Version != null) - { - // Try again with the version number appended to the folder name. - return dataFolderPath + "_" + Version.ToString(); - } - - return dataFolderPath; - } - } + public string ConfigurationFilePath => Path.Combine(_dataFolderPath, ConfigurationFileName); /// /// Gets the plugin configuration. From 46c7499e2b6e5897caeb562d4c0eba89aed843f2 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 23:55:23 +0000 Subject: [PATCH 59/77] reverted change --- MediaBrowser.Common/Plugins/BasePluginOfT.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index 24a6dbeacc..ea05a722bc 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -15,8 +15,6 @@ namespace MediaBrowser.Common.Plugins public abstract class BasePlugin : BasePlugin, IHasPluginConfiguration where TConfigurationType : BasePluginConfiguration { - private readonly string _dataFolderPath; - /// /// The configuration sync lock. /// @@ -49,14 +47,14 @@ namespace MediaBrowser.Common.Plugins var assemblyName = assembly.GetName(); var assemblyFilePath = assembly.Location; - _dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - if (!Directory.Exists(_dataFolderPath) && Version != null) + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + if (!Directory.Exists(dataFolderPath) && Version != null) { // Try again with the version number appended to the folder name. - _dataFolderPath = _dataFolderPath + "_" + Version.ToString(); + dataFolderPath = dataFolderPath + "_" + Version.ToString(); } - assemblyPlugin.SetAttributes(assemblyFilePath, _dataFolderPath, assemblyName.Version); + assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); if (idAttributes.Length > 0) @@ -139,7 +137,7 @@ namespace MediaBrowser.Common.Plugins /// Gets the full path to the configuration file. /// /// The configuration file path. - public string ConfigurationFilePath => Path.Combine(_dataFolderPath, ConfigurationFileName); + public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName); /// /// Gets the plugin configuration. From 53e280b80fe0e0e5ffdcf636246aa4afdc9467a0 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 20 Dec 2020 16:29:28 +0000 Subject: [PATCH 60/77] json name override. --- MediaBrowser.Common/Plugins/PluginManifest.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index 9c45e5d950..f938214293 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -13,68 +13,80 @@ namespace MediaBrowser.Common.Plugins /// /// Gets or sets the category of the plugin. /// + [JsonPropertyName("category")] public string Category { get; set; } = string.Empty; /// /// Gets or sets the changelog information. /// + [JsonPropertyName("changelog")] public string Changelog { get; set; } = string.Empty; /// /// Gets or sets the description of the plugin. /// + [JsonPropertyName("description")] public string Description { get; set; } = string.Empty; /// /// Gets or sets the Global Unique Identifier for the plugin. /// - [JsonPropertyName("Guid")] + [JsonPropertyName("guid")] public Guid Id { get; set; } /// /// Gets or sets the Name of the plugin. /// + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; /// /// Gets or sets an overview of the plugin. /// + [JsonPropertyName("overview")] public string Overview { get; set; } = string.Empty; /// /// Gets or sets the owner of the plugin. /// + [JsonPropertyName("owner")] public string Owner { get; set; } = string.Empty; /// /// Gets or sets the compatibility version for the plugin. /// + [JsonPropertyName("targetAbi")] public string TargetAbi { get; set; } = string.Empty; /// /// Gets or sets the timestamp of the plugin. /// + [JsonPropertyName("timestamp")] public DateTime Timestamp { get; set; } /// /// Gets or sets the Version number of the plugin. /// + [JsonPropertyName("version")] public string Version { get; set; } = string.Empty; /// /// Gets or sets a value indicating the operational status of this plugin. /// + [JsonPropertyName("status")] public PluginStatus Status { get; set; } /// /// Gets or sets a value indicating whether this plugin should automatically update. /// + [JsonPropertyName("autoUpdate")] public bool AutoUpdate { get; set; } = true; /// /// Gets or sets a value indicating whether this plugin has an image. /// Image must be located in the local plugin folder. /// + [JsonPropertyName("imagePath")] public string? ImagePath { get; set; } } } From 7a667619819bec314fdb6d0b4fd0c3290566297e Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 20 Dec 2020 19:30:48 +0000 Subject: [PATCH 61/77] write json files indented. --- Emby.Server.Implementations/Plugins/PluginManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 32646d1bf2..2497cddd9f 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -50,6 +50,7 @@ namespace Emby.Server.Implementations.Plugins _pluginsPath = pluginsPath; _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); _jsonOptions = JsonDefaults.GetOptions(); + _jsonOptions.WriteIndented = true; _config = config; _appHost = appHost; _minimumVersion = new Version(0, 0, 0, 1); From 3633996a53385d78601df33286b47415475ae3bb Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 21 Dec 2020 09:01:59 +0000 Subject: [PATCH 62/77] New json converter implemented. --- .../Plugins/PluginManager.cs | 2 ++ .../Json/Converters/JsonGuidDashConverter.cs | 26 +++++++++++++++++++ MediaBrowser.Common/Plugins/PluginManifest.cs | 2 ++ 3 files changed, 30 insertions(+) create mode 100644 MediaBrowser.Common/Json/Converters/JsonGuidDashConverter.cs diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 2497cddd9f..b46c199950 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -9,6 +9,7 @@ using System.Text.Json; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; +using MediaBrowser.Common.Json.Converters; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Plugins; @@ -50,6 +51,7 @@ namespace Emby.Server.Implementations.Plugins _pluginsPath = pluginsPath; _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); _jsonOptions = JsonDefaults.GetOptions(); + _jsonOptions.Converters.Add(new JsonGuidDashConverter()); _jsonOptions.WriteIndented = true; _config = config; _appHost = appHost; diff --git a/MediaBrowser.Common/Json/Converters/JsonGuidDashConverter.cs b/MediaBrowser.Common/Json/Converters/JsonGuidDashConverter.cs new file mode 100644 index 0000000000..75bab58751 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonGuidDashConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Converts a GUID object or value to/from JSON. + /// + public class JsonGuidDashConverter : JsonConverter + { + /// + public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var guidStr = reader.GetString(); + return guidStr == null ? Guid.Empty : new Guid(guidStr); + } + + /// + public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } +} diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index f938214293..956a91f851 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -1,6 +1,7 @@ #nullable enable using System; using System.Text.Json.Serialization; +using MediaBrowser.Common.Json.Converters; using MediaBrowser.Model.Plugins; namespace MediaBrowser.Common.Plugins @@ -32,6 +33,7 @@ namespace MediaBrowser.Common.Plugins /// Gets or sets the Global Unique Identifier for the plugin. /// [JsonPropertyName("guid")] + [JsonConverter(typeof(JsonGuidDashConverter))] public Guid Id { get; set; } /// From 621e6d28cda49fac580cf8c9c672b5fdfd3f743b Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 22 Dec 2020 14:07:01 +0000 Subject: [PATCH 63/77] Fallback to default guid --- .../Plugins/PluginManager.cs | 12 +++++- .../Json/Converters/JsonGuidDashConverter.cs | 26 ------------ MediaBrowser.Common/Plugins/PluginManifest.cs | 40 ++++++++++++++----- 3 files changed, 40 insertions(+), 38 deletions(-) delete mode 100644 MediaBrowser.Common/Json/Converters/JsonGuidDashConverter.cs diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index b46c199950..33faa5e9d8 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -51,8 +51,18 @@ namespace Emby.Server.Implementations.Plugins _pluginsPath = pluginsPath; _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); _jsonOptions = JsonDefaults.GetOptions(); - _jsonOptions.Converters.Add(new JsonGuidDashConverter()); _jsonOptions.WriteIndented = true; + + // We need to use the default GUID converter, so we need to remove any custom ones. + for (int a = _jsonOptions.Converters.Count - 1; a >= 0; a--) + { + if (_jsonOptions.Converters[a] is JsonGuidConverter convertor) + { + _jsonOptions.Converters.Remove(convertor); + break; + } + } + _config = config; _appHost = appHost; _minimumVersion = new Version(0, 0, 0, 1); diff --git a/MediaBrowser.Common/Json/Converters/JsonGuidDashConverter.cs b/MediaBrowser.Common/Json/Converters/JsonGuidDashConverter.cs deleted file mode 100644 index 75bab58751..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonGuidDashConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Globalization; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// - /// Converts a GUID object or value to/from JSON. - /// - public class JsonGuidDashConverter : JsonConverter - { - /// - public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var guidStr = reader.GetString(); - return guidStr == null ? Guid.Empty : new Guid(guidStr); - } - - /// - public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString()); - } - } -} diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index 956a91f851..334c8d9082 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -1,7 +1,7 @@ #nullable enable + using System; using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; using MediaBrowser.Model.Plugins; namespace MediaBrowser.Common.Plugins @@ -11,54 +11,71 @@ namespace MediaBrowser.Common.Plugins /// public class PluginManifest { + /// + /// Initializes a new instance of the class. + /// + public PluginManifest() + { + Category = string.Empty; + Changelog = string.Empty; + Description = string.Empty; + Status = PluginStatus.Active; + Id = Guid.Empty; + Name = string.Empty; + Owner = string.Empty; + Overview = string.Empty; + TargetAbi = string.Empty; + Version = string.Empty; + AutoUpdate = true; + } + /// /// Gets or sets the category of the plugin. /// [JsonPropertyName("category")] - public string Category { get; set; } = string.Empty; + public string Category { get; set; } /// /// Gets or sets the changelog information. /// [JsonPropertyName("changelog")] - public string Changelog { get; set; } = string.Empty; + public string Changelog { get; set; } /// /// Gets or sets the description of the plugin. /// [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + public string Description { get; set; } /// /// Gets or sets the Global Unique Identifier for the plugin. /// [JsonPropertyName("guid")] - [JsonConverter(typeof(JsonGuidDashConverter))] public Guid Id { get; set; } /// /// Gets or sets the Name of the plugin. /// [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + public string Name { get; set; } /// /// Gets or sets an overview of the plugin. /// [JsonPropertyName("overview")] - public string Overview { get; set; } = string.Empty; + public string Overview { get; set; } /// /// Gets or sets the owner of the plugin. /// [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; + public string Owner { get; set; } /// /// Gets or sets the compatibility version for the plugin. /// [JsonPropertyName("targetAbi")] - public string TargetAbi { get; set; } = string.Empty; + public string TargetAbi { get; set; } /// /// Gets or sets the timestamp of the plugin. @@ -70,7 +87,7 @@ namespace MediaBrowser.Common.Plugins /// Gets or sets the Version number of the plugin. /// [JsonPropertyName("version")] - public string Version { get; set; } = string.Empty; + public string Version { get; set; } /// /// Gets or sets a value indicating the operational status of this plugin. @@ -82,9 +99,10 @@ namespace MediaBrowser.Common.Plugins /// Gets or sets a value indicating whether this plugin should automatically update. /// [JsonPropertyName("autoUpdate")] - public bool AutoUpdate { get; set; } = true; + public bool AutoUpdate { get; set; } /// + /// Gets or sets the ImagePath /// Gets or sets a value indicating whether this plugin has an image. /// Image must be located in the local plugin folder. /// From 1f2ecd0775f291457e780733339bb5009ebb8408 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 22 Dec 2020 15:01:26 +0000 Subject: [PATCH 64/77] Fix for DI. --- Emby.Server.Implementations/Plugins/PluginManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 33faa5e9d8..fbb092a7c6 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Plugins { foreach (var pluginServiceRegistrator in _appHost.GetExportTypes()) { - var plugin = GetPluginByType(pluginServiceRegistrator.Assembly.GetType()); + var plugin = GetPluginByAssembly(pluginServiceRegistrator.Assembly); if (plugin == null) { _logger.LogError("Unable to find plugin in assembly {Assembly}", pluginServiceRegistrator.Assembly.FullName); @@ -350,14 +350,14 @@ namespace Emby.Server.Implementations.Plugins } /// - /// Finds the plugin record using the type. + /// Finds the plugin record using the assembly. /// - /// The being sought. + /// The being sought. /// The matching record, or null if not found. - private LocalPlugin? GetPluginByType(Type type) + private LocalPlugin? GetPluginByAssembly(Assembly assembly) { // Find which plugin it is by the path. - return _plugins.FirstOrDefault(p => string.Equals(p.Path, Path.GetDirectoryName(type.Assembly.Location), StringComparison.Ordinal)); + return _plugins.FirstOrDefault(p => string.Equals(p.Path, Path.GetDirectoryName(assembly.Location), StringComparison.Ordinal)); } /// @@ -368,7 +368,7 @@ namespace Emby.Server.Implementations.Plugins private IPlugin? CreatePluginInstance(Type type) { // Find the record for this plugin. - var plugin = GetPluginByType(type); + var plugin = GetPluginByAssembly(type.Assembly); if (plugin?.Manifest.Status < PluginStatus.Active) { return null; From 66d98cb8e406d1dddebd339b0aab20b93df4bf7b Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 10:24:30 +0000 Subject: [PATCH 65/77] Update Emby.Server.Implementations/ApplicationHost.cs Co-authored-by: Claus Vium --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b91ba6b6ca..f73cd1ea4f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -462,7 +462,7 @@ namespace Emby.Server.Implementations { // Convert to list so this isn't executed for each iteration var parts = GetExportTypes() - .Select(i => defaultFunc(i)) + .Select(defaultFunc) .Where(i => i != null) .Cast() .ToList(); From 4ba4eefeeb145d03c1599c12a1ef61b4f87f67fb Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 10:26:02 +0000 Subject: [PATCH 66/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Claus Vium --- Emby.Server.Implementations/Plugins/PluginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index fbb092a7c6..2609f6fca6 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -232,9 +232,9 @@ namespace Emby.Server.Implementations.Plugins var plugins = _plugins.Where(p => p.Id.Equals(id)); plugin = plugins.FirstOrDefault(p => p.Instance != null); - if (plugin == null) + if (plugin == null && plugins.Length > 0) { - plugin = plugins.OrderByDescending(p => p.Version).FirstOrDefault(); + plugin = plugins.OrderByDescending(p => p.Version)[0]; } } else From 63c290f87893d234e8a1db3ce2260feaab5e2304 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 10:26:20 +0000 Subject: [PATCH 67/77] Update Emby.Server.Implementations/Updates/InstallationManager.cs Co-authored-by: Claus Vium --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 4c2424c15b..76d3481b40 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -395,7 +395,7 @@ namespace Emby.Server.Implementations.Updates if (plugin.Instance?.CanUninstall == false) { - _logger.LogWarning("Attempt to delete non removable plugin {0}, ignoring request", plugin.Name); + _logger.LogWarning("Attempt to delete non removable plugin {PluginName}, ignoring request", plugin.Name); return; } From 8e04e6c837e628fe80fab486adea33ae3b33aae5 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 10:27:27 +0000 Subject: [PATCH 68/77] Update Emby.Server.Implementations/Updates/InstallationManager.cs Co-authored-by: Claus Vium --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 76d3481b40..abcb4313f3 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -589,7 +589,7 @@ namespace Emby.Server.Implementations.Updates await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); // Do plugin-specific processing - _logger.LogInformation(plugin == null ? "New plugin installed: {0} {1}" : "Plugin updated: {0} {1}", package.Name, package.Version); + _logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version); return plugin != null; } From 9a97933499d4171e6ff7d2d860845c1cc54ef7d8 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 10:29:21 +0000 Subject: [PATCH 69/77] Update Emby.Server.Implementations/Plugins/PluginManager.cs Co-authored-by: Claus Vium --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 2609f6fca6..dac3100af8 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -229,7 +229,7 @@ namespace Emby.Server.Implementations.Plugins if (version == null) { // If no version is given, return the current instance. - var plugins = _plugins.Where(p => p.Id.Equals(id)); + var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); plugin = plugins.FirstOrDefault(p => p.Instance != null); if (plugin == null && plugins.Length > 0) From e8df9551ef392785c0fb7269b9e187cd199390d8 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 10:31:11 +0000 Subject: [PATCH 70/77] Update PluginManager.cs Changed a to i --- Emby.Server.Implementations/Plugins/PluginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index dac3100af8..629975abb1 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -81,9 +81,9 @@ namespace Emby.Server.Implementations.Plugins public IEnumerable LoadAssemblies() { // Attempt to remove any deleted plugins and change any successors to be active. - for (int a = _plugins.Count - 1; a >= 0; a--) + for (int i = _plugins.Count - 1; i >= 0; i--) { - var plugin = _plugins[a]; + var plugin = _plugins[i]; if (plugin.Manifest.Status == PluginStatus.Deleted && DeletePlugin(plugin)) { UpdateSuccessors(plugin); From d98f42a6aa80b4d9f5f9ffecc17f87bd2510442a Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 10:36:34 +0000 Subject: [PATCH 71/77] Update PackageInfo.cs uncommented attribute. --- MediaBrowser.Model/Updates/PackageInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index 0902e04409..2b6e940d15 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Model.Updates /// Gets or sets a long description of the plugin containing features or helpful explanations. /// /// The description. - /// [JsonPropertyName("description")] + [JsonPropertyName("description")] public string Description { get; set; } /// From 62702fa3eb5070ce8c57dc4e39551bcc4e64fa74 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 16:28:50 +0000 Subject: [PATCH 72/77] Changes as requested --- .../ApplicationHost.cs | 2 +- .../Plugins/PluginManager.cs | 45 +++++++++++++------ Jellyfin.Api/Controllers/PluginsController.cs | 16 ++----- Jellyfin.Api/Models/ConfigurationPageInfo.cs | 1 - MediaBrowser.Common/Plugins/BasePluginOfT.cs | 20 +++------ .../Plugins/IHasPluginConfiguration.cs | 6 --- MediaBrowser.Common/Plugins/IPluginManager.cs | 1 + MediaBrowser.Common/Plugins/PluginManifest.cs | 4 +- 8 files changed, 43 insertions(+), 52 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f73cd1ea4f..b91ba6b6ca 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -462,7 +462,7 @@ namespace Emby.Server.Implementations { // Convert to list so this isn't executed for each iteration var parts = GetExportTypes() - .Select(defaultFunc) + .Select(i => defaultFunc(i)) .Where(i => i != null) .Cast() .ToList(); diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 629975abb1..c063597575 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Text; using System.Text.Json; +using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; @@ -86,7 +87,8 @@ namespace Emby.Server.Implementations.Plugins var plugin = _plugins[i]; if (plugin.Manifest.Status == PluginStatus.Deleted && DeletePlugin(plugin)) { - UpdateSuccessors(plugin); + // See if there is another version, and if so make that active. + ProcessAlternative(plugin); } } @@ -208,12 +210,19 @@ namespace Emby.Server.Implementations.Plugins if (DeletePlugin(plugin)) { + ProcessAlternative(plugin); return true; } _logger.LogWarning("Unable to delete {Path}, so marking as deleteOnStartup.", plugin.Path); // Unable to delete, so disable. - return ChangePluginState(plugin, PluginStatus.Deleted); + if (ChangePluginState(plugin, PluginStatus.Deleted)) + { + ProcessAlternative(plugin); + return true; + } + + return false; } /// @@ -232,9 +241,9 @@ namespace Emby.Server.Implementations.Plugins var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); plugin = plugins.FirstOrDefault(p => p.Instance != null); - if (plugin == null && plugins.Length > 0) + if (plugin == null) { - plugin = plugins.OrderByDescending(p => p.Version)[0]; + plugin = plugins.OrderByDescending(p => p.Version).FirstOrDefault(); } } else @@ -259,7 +268,8 @@ namespace Emby.Server.Implementations.Plugins if (ChangePluginState(plugin, PluginStatus.Active)) { - UpdateSuccessors(plugin); + // See if there is another version, and if so, supercede it. + ProcessAlternative(plugin); } } @@ -277,7 +287,8 @@ namespace Emby.Server.Implementations.Plugins // Update the manifest on disk if (ChangePluginState(plugin, PluginStatus.Disabled)) { - UpdateSuccessors(plugin); + // If there is another version, activate it. + ProcessAlternative(plugin); } } @@ -639,27 +650,33 @@ namespace Emby.Server.Implementations.Plugins /// Changes the status of the other versions of the plugin to "Superceded". /// /// The that's master. - private void UpdateSuccessors(LocalPlugin plugin) + private void ProcessAlternative(LocalPlugin plugin) { - // This value is memory only - so that the web will show restart required. - plugin.Manifest.Status = PluginStatus.Restart; - // Detect whether there is another version of this plugin that needs disabling. - var predecessor = _plugins.OrderByDescending(p => p.Version) + var previousVersion = _plugins.OrderByDescending(p => p.Version) .FirstOrDefault( p => p.Id.Equals(plugin.Id) && p.IsEnabledAndSupported && p.Version != plugin.Version); - if (predecessor == null) + if (previousVersion == null) { + // This value is memory only - so that the web will show restart required. + plugin.Manifest.Status = PluginStatus.Restart; return; } - if (predecessor.Manifest.Status == PluginStatus.Active && !ChangePluginState(predecessor, PluginStatus.Superceded)) + if (plugin.Manifest.Status == PluginStatus.Active && !ChangePluginState(previousVersion, PluginStatus.Superceded)) + { + _logger.LogError("Unable to enable version {Version} of {Name}", previousVersion.Version, previousVersion.Name); + } + else if (plugin.Manifest.Status == PluginStatus.Superceded && !ChangePluginState(previousVersion, PluginStatus.Active)) { - _logger.LogError("Unable to disable version {Version} of {Name}", predecessor.Version, predecessor.Name); + _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; } } } diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 365bb2248c..b73611c979 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -198,7 +198,7 @@ namespace Jellyfin.Api.Controllers /// Plugin id. /// Plugin uninstalled. /// Plugin not found. - /// An on success, or a if the file could not be found. + /// An on success, or a if the plugin could not be found. [HttpDelete("{pluginId}")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] @@ -210,7 +210,7 @@ namespace Jellyfin.Api.Controllers var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)); // Select the un-instanced one first. - var plugin = plugins.FirstOrDefault(p => p.Instance != null); + var plugin = plugins.FirstOrDefault(p => p.Instance == null); if (plugin == null) { // Then by the status. @@ -256,11 +256,7 @@ namespace Jellyfin.Api.Controllers /// Plugin id. /// Plugin configuration updated. /// Plugin not found or plugin does not have configuration. - /// - /// A that represents the asynchronous operation to update plugin configuration. - /// The task result contains an indicating success, or - /// when plugin not found or plugin doesn't have configuration. - /// + /// An on success, or a if the plugin could not be found. [HttpPost("{pluginId}/Configuration")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -321,11 +317,7 @@ namespace Jellyfin.Api.Controllers /// Plugin id. /// Plugin manifest returned. /// Plugin not found. - /// - /// A that represents the asynchronous operation to get the plugin's manifest. - /// The task result contains an indicating success, or - /// when plugin not found. - /// + /// A on success, or a if the plugin could not be found. [HttpPost("{pluginId}/Manifest")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index c15ed05d39..f56ef59765 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -23,7 +23,6 @@ namespace Jellyfin.Api.Models if (page.Plugin != null) { DisplayName = page.Plugin.Name; - // Don't use "N" because it needs to match Plugin.Id PluginId = page.Plugin.Id; } } diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index ea05a722bc..d5c7808512 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -25,8 +25,6 @@ namespace MediaBrowser.Common.Plugins /// private readonly object _configurationSaveLock = new object(); - private Action _directoryCreateFn; - /// /// The configuration. /// @@ -65,11 +63,6 @@ namespace MediaBrowser.Common.Plugins assemblyPlugin.SetId(assemblyId); } } - - if (this is IHasPluginConfiguration hasPluginConfiguration) - { - hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); - } } /// @@ -145,13 +138,6 @@ namespace MediaBrowser.Common.Plugins /// The configuration. BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration; - /// - public void SetStartupInfo(Action directoryCreateFn) - { - // hack alert, until the .net core transition is complete - _directoryCreateFn = directoryCreateFn; - } - /// /// Saves the current configuration to the file system. /// @@ -160,7 +146,11 @@ namespace MediaBrowser.Common.Plugins { lock (_configurationSaveLock) { - _directoryCreateFn(Path.GetDirectoryName(ConfigurationFilePath)); + var folder = Path.GetDirectoryName(ConfigurationFilePath); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } XmlSerializer.SerializeToFile(config, ConfigurationFilePath); } diff --git a/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs b/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs index 42ad85dd37..af9272caae 100644 --- a/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs +++ b/MediaBrowser.Common/Plugins/IHasPluginConfiguration.cs @@ -23,11 +23,5 @@ namespace MediaBrowser.Common.Plugins /// /// The configuration. void UpdateConfiguration(BasePluginConfiguration configuration); - - /// - /// Sets the startup directory creation function. - /// - /// The directory function used to create the configuration folder. - void SetStartupInfo(Action directoryCreateFn); } } diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index 7f7381b033..3da34d8bb3 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common.Plugins diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index 334c8d9082..4c724f6949 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -19,14 +19,12 @@ namespace MediaBrowser.Common.Plugins Category = string.Empty; Changelog = string.Empty; Description = string.Empty; - Status = PluginStatus.Active; Id = Guid.Empty; Name = string.Empty; Owner = string.Empty; Overview = string.Empty; TargetAbi = string.Empty; Version = string.Empty; - AutoUpdate = true; } /// @@ -99,7 +97,7 @@ namespace MediaBrowser.Common.Plugins /// Gets or sets a value indicating whether this plugin should automatically update. /// [JsonPropertyName("autoUpdate")] - public bool AutoUpdate { get; set; } + public bool AutoUpdate { get; set; } = true; // DO NOT MOVE THIS INTO THE CONSTRUCTOR. /// /// Gets or sets the ImagePath From dae6798a1821924d38fe8d8d387b4981ed03e164 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 17:25:41 +0000 Subject: [PATCH 73/77] Making it work --- Emby.Server.Implementations/ApplicationHost.cs | 2 -- MediaBrowser.Common/Json/JsonDefaults.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b91ba6b6ca..b88ccff199 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -118,7 +118,6 @@ namespace Emby.Server.Implementations private readonly IFileSystem _fileSystemManager; private readonly IXmlSerializer _xmlSerializer; - private readonly IJsonSerializer _jsonSerializer; private readonly IStartupOptions _startupOptions; private readonly IPluginManager _pluginManager; @@ -249,7 +248,6 @@ namespace Emby.Server.Implementations IServiceCollection serviceCollection) { _xmlSerializer = new MyXmlSerializer(); - _jsonSerializer = new JsonSerializer(); ServiceCollection = serviceCollection; diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 2b5f04cf82..67c3dfe546 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Common.Json WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, NumberHandling = JsonNumberHandling.AllowReadingFromString, - + PropertyNameCaseInsensitive = true, Converters = { new JsonGuidConverter(), From 21f6d3943264b0f6a59f3312e9ec34a3b6269af2 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 17:43:29 +0000 Subject: [PATCH 74/77] copy constructor --- Emby.Server.Implementations/Plugins/PluginManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index c063597575..51a75f6a39 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -51,8 +51,10 @@ namespace Emby.Server.Implementations.Plugins _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _pluginsPath = pluginsPath; _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); - _jsonOptions = JsonDefaults.GetOptions(); - _jsonOptions.WriteIndented = true; + _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions()) + { + WriteIndented = true + }; // We need to use the default GUID converter, so we need to remove any custom ones. for (int a = _jsonOptions.Converters.Count - 1; a >= 0; a--) From dbfbf9fb5bc79f4c23552a0a881c55c4300ab75b Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 30 Dec 2020 19:45:31 -0700 Subject: [PATCH 75/77] Fix bad merge --- .../ApplicationHost.cs | 96 ------------------- 1 file changed, 96 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 9d4643fcf5..094134318c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1026,102 +1026,6 @@ namespace Emby.Server.Implementations protected abstract void RestartInternal(); - /// - public IEnumerable GetLocalPlugins(string path, bool cleanup = true) - { - var minimumVersion = new Version(0, 0, 0, 1); - var versions = new List(); - if (!Directory.Exists(path)) - { - // Plugin path doesn't exist, don't try to enumerate subfolders. - return Enumerable.Empty(); - } - - var directories = Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly); - - foreach (var dir in directories) - { - try - { - var metafile = Path.Combine(dir, "meta.json"); - if (File.Exists(metafile)) - { - var jsonString = File.ReadAllText(metafile, Encoding.UTF8); - var manifest = JsonSerializer.Deserialize(jsonString, _jsonOptions); - - if (!Version.TryParse(manifest.TargetAbi, out var targetAbi)) - { - targetAbi = minimumVersion; - } - - if (!Version.TryParse(manifest.Version, out var version)) - { - version = minimumVersion; - } - - if (ApplicationVersion >= targetAbi) - { - // Only load Plugins if the plugin is built for this version or below. - versions.Add(new LocalPlugin(manifest.Guid, manifest.Name, version, dir)); - } - } - else - { - // No metafile, so lets see if the folder is versioned. - metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1]; - - int versionIndex = dir.LastIndexOf('_'); - if (versionIndex != -1 && Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version parsedVersion)) - { - // Versioned folder. - versions.Add(new LocalPlugin(Guid.Empty, metafile, parsedVersion, dir)); - } - else - { - // Un-versioned folder - Add it under the path name and version 0.0.0.1. - versions.Add(new LocalPlugin(Guid.Empty, metafile, minimumVersion, dir)); - } - } - } - catch - { - continue; - } - } - - string lastName = string.Empty; - versions.Sort(LocalPlugin.Compare); - // Traverse backwards through the list. - // The first item will be the latest version. - for (int x = versions.Count - 1; x >= 0; x--) - { - if (!string.Equals(lastName, versions[x].Name, StringComparison.OrdinalIgnoreCase)) - { - versions[x].DllFiles.AddRange(Directory.EnumerateFiles(versions[x].Path, "*.dll", SearchOption.AllDirectories)); - lastName = versions[x].Name; - continue; - } - - if (!string.IsNullOrEmpty(lastName) && cleanup) - { - // Attempt a cleanup of old folders. - try - { - Logger.LogDebug("Deleting {Path}", versions[x].Path); - Directory.Delete(versions[x].Path, true); - } - catch (Exception e) - { - Logger.LogWarning(e, "Unable to delete {Path}", versions[x].Path); - } - - versions.RemoveAt(x); - } - } - - return versions; - } - /// /// Gets the composable part assemblies. /// From 149c2b2169192da29d82c440175d8d9049dea8b3 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 31 Dec 2020 11:39:34 +0000 Subject: [PATCH 76/77] Added referenced assembly failure detection, and DI failure protection. --- Emby.Server.Implementations/ApplicationHost.cs | 2 ++ Emby.Server.Implementations/Plugins/PluginManager.cs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 094134318c..1b9bb86bbd 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -413,6 +413,8 @@ namespace Emby.Server.Implementations catch (Exception ex) { Logger.LogError(ex, "Error creating {Type}", type); + // If this is a plugin fail it. + _pluginManager.FailPlugin(type.Assembly); return null; } finally diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 51a75f6a39..1ab01252d0 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -111,6 +111,10 @@ namespace Emby.Server.Implementations.Plugins try { assembly = Assembly.LoadFrom(file); + + // This force loads all reference dll's that the plugin uses in the try..catch block. + // Removing this will cause JF to bomb out if referenced dll's cause issues. + assembly.GetExportedTypes(); } catch (FileLoadException ex) { From bd1c115e46795f7db38366d31de79bf2ff88ca8d Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 31 Dec 2020 15:59:48 +0000 Subject: [PATCH 77/77] renamed imagePath to imageUrl --- MediaBrowser.Model/Updates/PackageInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index 2b6e940d15..7a82685f0e 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -77,9 +77,9 @@ namespace MediaBrowser.Model.Updates #pragma warning restore CA2227 // Collection properties should be read only /// - /// Gets or sets the image path for the package. + /// Gets or sets the image url for the package. /// - [JsonPropertyName("imagePath")] - public string? ImagePath { get; set; } + [JsonPropertyName("imageUrl")] + public string? ImageUrl { get; set; } } }