From 0d2106a2727f572ef556845d6dd49c224d3d4d96 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 10 Dec 2020 11:36:31 -0700 Subject: Allow playlist to be created by query string --- Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs index d0d6889fc8..65d4b644e4 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Api.Models.PlaylistDtos /// /// Gets or sets the user id. /// - public Guid UserId { get; set; } + public Guid? UserId { get; set; } /// /// Gets or sets the media type. -- cgit v1.2.3 From c6b381db1098041fe975f9830728bf1f000ce71f Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 13 Dec 2020 08:32:33 -0700 Subject: Use request body for mapping xml channels --- Jellyfin.Api/Controllers/LiveTvController.cs | 11 +++------ .../Models/LiveTvDtos/SetChannelMappingDto.cs | 28 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 56d4b3933e..6f2d432277 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1119,20 +1119,15 @@ namespace Jellyfin.Api.Controllers /// /// Set channel mappings. /// - /// Provider id. - /// Tuner channel id. - /// Provider channel id. + /// The set channel mapping dto. /// Created channel mapping returned. /// An containing the created channel mapping. [HttpPost("ChannelMappings")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> SetChannelMapping( - [FromQuery] string? providerId, - [FromQuery] string? tunerChannelId, - [FromQuery] string? providerChannelId) + public async Task> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto) { - return await _liveTvManager.SetChannelMapping(providerId, tunerChannelId, providerChannelId).ConfigureAwait(false); + return await _liveTvManager.SetChannelMapping(setChannelMappingDto.ProviderId, setChannelMappingDto.TunerChannelId, setChannelMappingDto.ProviderChannelId).ConfigureAwait(false); } /// diff --git a/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs b/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs new file mode 100644 index 0000000000..2ddaa89e8b --- /dev/null +++ b/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.LiveTvDtos +{ + /// + /// Set channel mapping dto. + /// + public class SetChannelMappingDto + { + /// + /// Gets or sets the provider id. + /// + [Required] + public string ProviderId { get; set; } = string.Empty; + + /// + /// Gets or sets the tuner channel id. + /// + [Required] + public string TunerChannelId { get; set; } = string.Empty; + + /// + /// Gets or sets the provider channel id. + /// + [Required] + public string ProviderChannelId { get; set; } = string.Empty; + } +} \ No newline at end of file -- cgit v1.2.3 From 7986465cf785ca385fd1765326887e550bced033 Mon Sep 17 00:00:00 2001 From: Greenback Date: Sun, 6 Dec 2020 23:48:54 +0000 Subject: Initial upload --- Emby.Server.Implementations/ApplicationHost.cs | 250 ++------ .../Emby.Server.Implementations.csproj | 8 +- Emby.Server.Implementations/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 Emby.Server.Implementations/Plugins/Superceded.png | Bin 0 -> 2136 bytes Emby.Server.Implementations/Plugins/blank.png | Bin 0 -> 120 bytes Emby.Server.Implementations/Plugins/disabled.png | Bin 0 -> 1790 bytes .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 2 +- .../Updates/InstallationManager.cs | 456 +++++++------- Jellyfin.Api/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 +- .../Events/Updates/PluginUninstalledEventArgs.cs | 7 +- MediaBrowser.Controller/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 (limited to 'Jellyfin.Api/Models') 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 0000000000..3722ee5200 Binary files /dev/null and b/Emby.Server.Implementations/Plugins/Active.png differ diff --git a/Emby.Server.Implementations/Plugins/Malfunction.png b/Emby.Server.Implementations/Plugins/Malfunction.png new file mode 100644 index 0000000000..d4726150eb Binary files /dev/null and b/Emby.Server.Implementations/Plugins/Malfunction.png differ diff --git a/Emby.Server.Implementations/Plugins/NotSupported.png b/Emby.Server.Implementations/Plugins/NotSupported.png new file mode 100644 index 0000000000..a13c1f7c1c Binary files /dev/null and b/Emby.Server.Implementations/Plugins/NotSupported.png differ 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 0000000000..65fd102a2c Binary files /dev/null and b/Emby.Server.Implementations/Plugins/RestartRequired.png differ diff --git a/Emby.Server.Implementations/Plugins/Superceded.png b/Emby.Server.Implementations/Plugins/Superceded.png new file mode 100644 index 0000000000..251e70535b Binary files /dev/null and b/Emby.Server.Implementations/Plugins/Superceded.png differ diff --git a/Emby.Server.Implementations/Plugins/blank.png b/Emby.Server.Implementations/Plugins/blank.png new file mode 100644 index 0000000000..f81ae32438 Binary files /dev/null and b/Emby.Server.Implementations/Plugins/blank.png differ diff --git a/Emby.Server.Implementations/Plugins/disabled.png b/Emby.Server.Implementations/Plugins/disabled.png new file mode 100644 index 0000000000..eeb8ffefc2 Binary files /dev/null and b/Emby.Server.Implementations/Plugins/disabled.png differ 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 -- cgit v1.2.3 From d2d45295fc2a948f9a6945f56197097d0f2ef9d6 Mon Sep 17 00:00:00 2001 From: Greenback Date: Mon, 14 Dec 2020 23:13:29 +0000 Subject: Rollback change. --- Jellyfin.Api/Models/ConfigurationPageInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Api/Models') 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(); -- cgit v1.2.3 From c197dca759ee7353d7e9f477b3cc189eac0c14e0 Mon Sep 17 00:00:00 2001 From: Greenback Date: Tue, 15 Dec 2020 18:27:31 +0000 Subject: 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(-) (limited to 'Jellyfin.Api/Models') 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; } } -- cgit v1.2.3 From 4539164d3892701d6d358c558df5359b282a3568 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 19 Dec 2020 10:55:07 -0700 Subject: Add request parameters to OpenLiveStreamDto --- Jellyfin.Api/Controllers/MediaInfoController.cs | 28 +++++------ .../Models/MediaInfoDtos/OpenLiveStreamDto.cs | 55 ++++++++++++++++++++++ 2 files changed, 69 insertions(+), 14 deletions(-) (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index 2a1da31c9e..baa2e06361 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -259,24 +259,24 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? subtitleStreamIndex, [FromQuery] int? maxAudioChannels, [FromQuery] Guid? itemId, - [FromBody] OpenLiveStreamDto openLiveStreamDto, - [FromQuery] bool enableDirectPlay = true, - [FromQuery] bool enableDirectStream = true) + [FromBody] OpenLiveStreamDto? openLiveStreamDto, + [FromQuery] bool? enableDirectPlay, + [FromQuery] bool? enableDirectStream) { var request = new LiveStreamRequest { - OpenToken = openToken, - UserId = userId ?? Guid.Empty, - PlaySessionId = playSessionId, - MaxStreamingBitrate = maxStreamingBitrate, - StartTimeTicks = startTimeTicks, - AudioStreamIndex = audioStreamIndex, - SubtitleStreamIndex = subtitleStreamIndex, - MaxAudioChannels = maxAudioChannels, - ItemId = itemId ?? Guid.Empty, + OpenToken = openToken ?? openLiveStreamDto?.OpenToken, + UserId = userId ?? openLiveStreamDto?.UserId ?? Guid.Empty, + PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId, + MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate, + StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks, + AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex, + SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex, + MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels, + ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty, DeviceProfile = openLiveStreamDto?.DeviceProfile, - EnableDirectPlay = enableDirectPlay, - EnableDirectStream = enableDirectStream, + EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true, + EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true, DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http } }; return await _mediaInfoHelper.OpenMediaSource(Request, request).ConfigureAwait(false); diff --git a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs index b0b3de8553..7045423261 100644 --- a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs +++ b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs @@ -10,6 +10,61 @@ namespace Jellyfin.Api.Models.MediaInfoDtos /// public class OpenLiveStreamDto { + /// + /// Gets or sets the open token. + /// + public string? OpenToken { get; set; } + + /// + /// Gets or sets the user id. + /// + public Guid? UserId { get; set; } + + /// + /// Gets or sets the play session id. + /// + public string? PlaySessionId { get; set; } + + /// + /// Gets or sets the max streaming bitrate. + /// + public int? MaxStreamingBitrate { get; set; } + + /// + /// Gets or sets the start time in ticks. + /// + public long? StartTimeTicks { get; set; } + + /// + /// Gets or sets the audio stream index. + /// + public int? AudioStreamIndex { get; set; } + + /// + /// Gets or sets the subtitle stream index. + /// + public int? SubtitleStreamIndex { get; set; } + + /// + /// Gets or sets the max audio channels. + /// + public int? MaxAudioChannels { get; set; } + + /// + /// Gets or sets the item id. + /// + public Guid? ItemId { get; set; } + + /// + /// Gets or sets a value indicating whether to enable direct play. + /// + public bool? EnableDirectPlay { get; set; } + + /// + /// Gets or sets a value indicating whether to enale direct stream. + /// + public bool? EnableDirectStream { get; set; } + /// /// Gets or sets the device profile. /// -- cgit v1.2.3 From 62702fa3eb5070ce8c57dc4e39551bcc4e64fa74 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 23 Dec 2020 16:28:50 +0000 Subject: Changes as requested --- Emby.Server.Implementations/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(-) (limited to 'Jellyfin.Api/Models') 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 -- cgit v1.2.3 From c6aa6ceed97f6172d35d8be8feb9afc61a0e829d Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 12 Jan 2021 22:10:23 +0000 Subject: Removal of IPluginConfigurationPage --- Jellyfin.Api/Controllers/DashboardController.cs | 49 ++++------------------ Jellyfin.Api/Models/ConfigurationPageInfo.cs | 17 -------- .../Plugins/IPluginConfigurationPage.cs | 30 ------------- 3 files changed, 7 insertions(+), 89 deletions(-) (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index b77d79209a..8cb93cc80b 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -62,32 +62,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableInMainMenu, [FromQuery] ConfigurationPageType? pageType) { - const string unavailableMessage = "The server is still loading. Please try again momentarily."; - - var pages = _appHost.GetExports().ToList(); - - if (pages == null) - { - return NotFound(unavailableMessage); - } - - // Don't allow a failing plugin to fail them all - var configPages = pages.Select(p => - { - try - { - return new ConfigurationPageInfo(p); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting plugin information from {Plugin}", p.GetType().Name); - return null; - } - }) - .Where(i => i != null) - .ToList(); - - configPages.AddRange(_pluginManager.Plugins.SelectMany(GetConfigPages)); + var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList(); if (pageType.HasValue) { @@ -121,24 +96,14 @@ namespace Jellyfin.Api.Controllers var isJs = false; var isTemplate = false; - var page = _appHost.GetExports().FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); - if (page != null) + var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase)); + if (altPage != null) { - plugin = page.Plugin; - stream = page.GetHtmlStream(); - } + plugin = altPage.Item2; + stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath); - if (plugin == null) - { - var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase)); - if (altPage != null) - { - plugin = altPage.Item2; - stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath); - - isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); - isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal); - } + isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); + isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal); } if (plugin != null && stream != null) diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index f56ef59765..0aa568fe74 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -10,23 +10,6 @@ namespace Jellyfin.Api.Models /// public class ConfigurationPageInfo { - /// - /// Initializes a new instance of the class. - /// - /// Instance of interface. - public ConfigurationPageInfo(IPluginConfigurationPage page) - { - Name = page.Name; - - ConfigurationPageType = page.ConfigurationPageType; - - if (page.Plugin != null) - { - DisplayName = page.Plugin.Name; - PluginId = page.Plugin.Id; - } - } - /// /// Initializes a new instance of the class. /// diff --git a/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs b/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs index 93eab42cc7..38e953be5e 100644 --- a/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs +++ b/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs @@ -3,36 +3,6 @@ using MediaBrowser.Common.Plugins; namespace MediaBrowser.Controller.Plugins { - /// - /// Interface IConfigurationPage. - /// - public interface IPluginConfigurationPage - { - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - - /// - /// Gets the type of the configuration page. - /// - /// The type of the configuration page. - ConfigurationPageType ConfigurationPageType { get; } - - /// - /// Gets the plugin. - /// - /// The plugin. - IPlugin Plugin { get; } - - /// - /// Gets the HTML stream. - /// - /// Stream. - Stream GetHtmlStream(); - } - /// /// Enum ConfigurationPageType. /// -- cgit v1.2.3 From a4e838fbf594020e9e0663e27d4aab878393c3f3 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 15 Jan 2021 10:36:44 +0000 Subject: Remoed configurationPageType --- Jellyfin.Api/Controllers/DashboardController.cs | 9 +-------- Jellyfin.Api/Models/ConfigurationPageInfo.cs | 6 ------ .../Plugins/ConfigurationPageType.cs.cs | 21 --------------------- 3 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 MediaBrowser.Controller/Plugins/ConfigurationPageType.cs.cs (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index 8cb93cc80b..ff7895373b 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -51,7 +51,6 @@ namespace Jellyfin.Api.Controllers /// Gets the configuration pages. /// /// Whether to enable in the main menu. - /// The . /// ConfigurationPages returned. /// Server still loading. /// An with infos about the plugins. @@ -59,16 +58,10 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetConfigurationPages( - [FromQuery] bool? enableInMainMenu, - [FromQuery] ConfigurationPageType? pageType) + [FromQuery] bool? enableInMainMenu) { var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList(); - if (pageType.HasValue) - { - configPages = configPages.Where(p => p!.ConfigurationPageType == pageType).ToList(); - } - if (enableInMainMenu.HasValue) { configPages = configPages.Where(p => p!.EnableInMainMenu == enableInMainMenu.Value).ToList(); diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index 0aa568fe74..a7bbe42fe5 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -51,12 +51,6 @@ namespace Jellyfin.Api.Models /// public string? DisplayName { get; set; } - /// - /// Gets or sets the type of the configuration page. - /// - /// The type of the configuration page. - public ConfigurationPageType ConfigurationPageType { get; set; } - /// /// Gets or sets the plugin id. /// diff --git a/MediaBrowser.Controller/Plugins/ConfigurationPageType.cs.cs b/MediaBrowser.Controller/Plugins/ConfigurationPageType.cs.cs deleted file mode 100644 index 38e953be5e..0000000000 --- a/MediaBrowser.Controller/Plugins/ConfigurationPageType.cs.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.IO; -using MediaBrowser.Common.Plugins; - -namespace MediaBrowser.Controller.Plugins -{ - /// - /// Enum ConfigurationPageType. - /// - public enum ConfigurationPageType - { - /// - /// The plugin configuration. - /// - PluginConfiguration, - - /// - /// The none. - /// - None - } -} -- cgit v1.2.3 From 55670b91b26610655cd4c0528e3dd938b852b4fc Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 23 Jan 2021 17:32:13 -0700 Subject: Use ArrayModelBinder for sortBy and sortOrder --- Jellyfin.Api/Controllers/ChannelsController.cs | 7 +++--- Jellyfin.Api/Controllers/ItemsController.cs | 8 +++--- Jellyfin.Api/Controllers/LiveTvController.cs | 4 +-- Jellyfin.Api/Controllers/TrailersController.cs | 5 ++-- Jellyfin.Api/Controllers/YearsController.cs | 5 ++-- Jellyfin.Api/Helpers/RequestHelpers.cs | 31 +++++++----------------- Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs | 7 ++++-- 7 files changed, 29 insertions(+), 38 deletions(-) (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index b70c76e807..54bd800951 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; -using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -121,9 +120,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] string? sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, - [FromQuery] string? sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) { var user = userId.HasValue && !userId.Equals(Guid.Empty) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index b84136ac6f..7d77474958 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -175,7 +175,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] bool? recursive, [FromQuery] string? searchTerm, - [FromQuery] string? sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, @@ -184,7 +184,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, - [FromQuery] string? sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, [FromQuery] bool? isPlayed, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, @@ -608,7 +608,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] bool? recursive, [FromQuery] string? searchTerm, - [FromQuery] string? sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, @@ -617,7 +617,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, - [FromQuery] string? sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, [FromQuery] bool? isPlayed, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 6f2d432277..24ee833ef7 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -553,8 +553,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isSports, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] string? sortBy, - [FromQuery] string? sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, [FromQuery] bool? enableImages, diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 8e9ece14f8..242b8f068e 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -1,6 +1,7 @@ using System; using Jellyfin.Api.Constants; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -144,7 +145,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] bool? recursive, [FromQuery] string? searchTerm, - [FromQuery] string? sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, @@ -152,7 +153,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, - [FromQuery] string? sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, [FromQuery] bool? isPlayed, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 48c639b08d..7c27752f7f 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -7,6 +7,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -70,13 +71,13 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetYears( [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] string? sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, - [FromQuery] string? sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index efce11f8a7..056ad83daf 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; @@ -8,7 +9,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Http; @@ -25,35 +25,22 @@ namespace Jellyfin.Api.Helpers /// Sort By. Comma delimited string. /// Sort Order. Comma delimited string. /// Order By. - public static ValueTuple[] GetOrderBy(string? sortBy, string? requestedSortOrder) + public static ValueTuple[] GetOrderBy(IReadOnlyList sortBy, IReadOnlyList requestedSortOrder) { - var val = sortBy; - - if (string.IsNullOrEmpty(val)) + if (sortBy.Count == 0) { return Array.Empty>(); } - var vals = val.Split(','); - if (string.IsNullOrWhiteSpace(requestedSortOrder)) + var result = new ValueTuple[sortBy.Count]; + for (var i = 0; i < sortBy.Count; i++) { - requestedSortOrder = "Ascending"; - } - - var sortOrders = requestedSortOrder.Split(','); - - var result = new ValueTuple[vals.Length]; + var sortOrderIndex = requestedSortOrder.Count > i ? i : 0; - for (var i = 0; i < vals.Length; i++) - { - var sortOrderIndex = sortOrders.Length > i ? i : 0; - - var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; - var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) - ? SortOrder.Descending + var sortOrder = requestedSortOrder.Count > sortOrderIndex + ? requestedSortOrder[sortOrderIndex] : SortOrder.Ascending; - - result[i] = new ValueTuple(vals[i], sortOrder); + result[i] = new ValueTuple(sortBy[i], sortOrder); } return result; diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs index a47ae926c1..588ce717c8 100644 --- a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs +++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Json.Converters; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -106,12 +107,14 @@ namespace Jellyfin.Api.Models.LiveTvDtos /// Gets or sets specify one or more sort orders, comma delimited. Options: Name, StartDate. /// Optional. /// - public string? SortBy { get; set; } + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList SortBy { get; set; } = Array.Empty(); /// /// Gets or sets sort Order - Ascending,Descending. /// - public string? SortOrder { get; set; } + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList SortOrder { get; set; } = Array.Empty(); /// /// Gets or sets the genres to return guide information for. -- cgit v1.2.3 From eb0ff0c37077cc70def96713b257204179c15f80 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 6 Feb 2021 17:18:37 +0100 Subject: Fix forgot password pin request --- Jellyfin.Api/Controllers/UserController.cs | 6 +++--- Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 87a4ffd92e..17c28779a9 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -509,14 +509,14 @@ namespace Jellyfin.Api.Controllers /// /// Redeems a forgot password pin. /// - /// The pin. + /// The forgot password pin request containing the endered pin. /// Pin reset process started. /// A containing a . [HttpPost("ForgotPassword/Pin")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> ForgotPasswordPin([FromBody, Required] string pin) + public async Task> ForgotPasswordPin([FromBody, Required] ForgotPasswordPinDto forgotPasswordPinRequest) { - var result = await _userManager.RedeemPasswordResetPin(pin).ConfigureAwait(false); + var result = await _userManager.RedeemPasswordResetPin(forgotPasswordPinRequest.Pin).ConfigureAwait(false); return result; } diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs new file mode 100644 index 0000000000..62780e23c5 --- /dev/null +++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.UserDtos +{ + /// + /// Forgot Password Pin enter request body DTO. + /// + public class ForgotPasswordPinDto + { + /// + /// Gets or sets the entered pin to have the password reset. + /// + [Required] + public string? Pin { get; set; } + } +} -- cgit v1.2.3 From b1fe28d0a6c1bbc67543fc1b5877a69f7958f8c5 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 21 Feb 2021 02:58:30 +0100 Subject: Use GetEncodingOptions where possible --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 5 ----- Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs | 2 +- Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 +++--- 4 files changed, 5 insertions(+), 10 deletions(-) (limited to 'Jellyfin.Api/Models') diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 5ef83f2746..0760e81274 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -335,11 +335,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return new Uri(url).AbsoluteUri.TrimEnd('/'); } - protected EncodingOptions GetEncodingOptions() - { - return Config.GetConfiguration("encoding"); - } - private static string GetHdHrIdFromChannelId(string channelId) { return channelId.Split('_')[1]; diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs index 872a468245..e33e552edb 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs @@ -98,7 +98,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos private EncodingOptions GetOptions() { - return _config.GetConfiguration("encoding"); + return _config.GetEncodingOptions(); } private async void TimerCallback(object? state) diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index 0925a87b55..bf0225e988 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -32,7 +32,7 @@ namespace Jellyfin.Server.Migrations.Routines public void Perform() { // Set EnableThrottling to false since it wasn't used before and may introduce issues - var encoding = _configManager.GetConfiguration("encoding"); + var encoding = _configManager.GetEncodingOptions(); if (encoding.EnableThrottling) { _logger.LogInformation("Disabling transcoding throttling during migration"); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index c0b6cf28bb..45872b5c08 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public void SetFFmpegPath() { // 1) Custom path stored in config/encoding xml file under tag takes precedence - if (!ValidatePath(_configurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom)) + if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom)) { // 2) Check if the --ffmpeg CLI switch has been given if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument)) @@ -118,7 +118,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI - var config = _configurationManager.GetConfiguration("encoding"); + var config = _configurationManager.GetEncodingOptions(); config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; _configurationManager.SaveConfiguration("encoding", config); @@ -177,7 +177,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Write the new ffmpeg path to the xml as // This ensures its not lost on next startup - var config = _configurationManager.GetConfiguration("encoding"); + var config = _configurationManager.GetEncodingOptions(); config.EncoderAppPath = newPath; _configurationManager.SaveConfiguration("encoding", config); -- cgit v1.2.3 From 57102090d307a62b3759177df3ba545cfad6c327 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 22 Feb 2021 13:15:29 +0100 Subject: Add tests for DashboardController --- Jellyfin.Api/Controllers/DashboardController.cs | 42 ++++-------- Jellyfin.Api/Models/ConfigurationPageInfo.cs | 1 - Jellyfin.Server/CoreAppHost.cs | 1 - MediaBrowser.Common/Plugins/BasePlugin.cs | 3 - tests/Jellyfin.Api.Tests/BrandingServiceTests.cs | 49 -------------- .../Controllers/BrandingControllerTests.cs | 50 ++++++++++++++ .../Controllers/DashboardControllerTests.cs | 76 ++++++++++++++++++++++ tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 4 ++ .../JellyfinApplicationFactory.cs | 4 +- tests/Jellyfin.Api.Tests/TestAppHost.cs | 51 +++++++++++++++ tests/Jellyfin.Api.Tests/TestPage.html | 9 +++ tests/Jellyfin.Api.Tests/TestPlugin.cs | 43 ++++++++++++ 12 files changed, 246 insertions(+), 87 deletions(-) delete mode 100644 tests/Jellyfin.Api.Tests/BrandingServiceTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs create mode 100644 tests/Jellyfin.Api.Tests/TestAppHost.cs create mode 100644 tests/Jellyfin.Api.Tests/TestPage.html create mode 100644 tests/Jellyfin.Api.Tests/TestPlugin.cs (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index b2baa9cea0..0c19f28183 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -22,22 +22,18 @@ namespace Jellyfin.Api.Controllers public class DashboardController : BaseJellyfinApiController { 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, IPluginManager pluginManager) { _logger = logger; - _appHost = appHost; _pluginManager = pluginManager; } @@ -51,7 +47,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("web/ConfigurationPages")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult> GetConfigurationPages( + public ActionResult> GetConfigurationPages( [FromQuery] bool? enableInMainMenu) { var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList(); @@ -77,38 +73,22 @@ namespace Jellyfin.Api.Controllers [ProducesFile(MediaTypeNames.Text.Html, "application/x-javascript")] public ActionResult GetDashboardConfigurationPage([FromQuery] string? name) { - IPlugin? plugin = null; - Stream? stream = null; - - var isJs = false; - var isTemplate = false; - var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase)); - if (altPage != null) + if (altPage == null) { - plugin = altPage.Item2; - stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath); - - isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); - isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal); + return NotFound(); } - if (plugin != null && stream != null) + IPlugin plugin = altPage.Item2; + string resourcePath = altPage.Item1.EmbeddedResourcePath; + Stream? stream = plugin.GetType().Assembly.GetManifestResourceStream(resourcePath); + if (stream == null) { - if (isJs) - { - return File(stream, MimeTypes.GetMimeType("page.js")); - } - - if (isTemplate) - { - return File(stream, MimeTypes.GetMimeType("page.html")); - } - - return File(stream, MimeTypes.GetMimeType("page.html")); + _logger.LogError("Failed to get resource {Resource} from plugin {Plugin}", resourcePath, plugin.Name); + return NotFound(); } - return NotFound(); + return File(stream, MimeTypes.GetMimeType(resourcePath)); } private IEnumerable GetConfigPages(LocalPlugin plugin) @@ -120,7 +100,7 @@ namespace Jellyfin.Api.Controllers { if (plugin?.Instance is not IHasWebPages hasWebPages) { - return new List>(); + return Enumerable.Empty>(); } return hasWebPages.GetPages().Select(i => new Tuple(i, plugin.Instance)); diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index a7bbe42fe5..d214280292 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Common.Plugins; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; namespace Jellyfin.Api.Models diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index b76aa5e141..ae2fb39999 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -11,7 +11,6 @@ using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Users; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Drawing; diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index e228ae7ec5..7b162c0e1c 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -1,10 +1,7 @@ using System; using System.IO; using System.Reflection; -using System.Runtime.InteropServices; -using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Common.Plugins { diff --git a/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs b/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs deleted file mode 100644 index 1cbe94c5b9..0000000000 --- a/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Text.Json; -using System.Threading.Tasks; -using MediaBrowser.Model.Branding; -using Xunit; - -namespace Jellyfin.Api.Tests -{ - public sealed class BrandingServiceTests : IClassFixture - { - private readonly JellyfinApplicationFactory _factory; - - public BrandingServiceTests(JellyfinApplicationFactory factory) - { - _factory = factory; - } - - [Fact] - public async Task GetConfiguration_ReturnsCorrectResponse() - { - // Arrange - var client = _factory.CreateClient(); - - // Act - var response = await client.GetAsync("/Branding/Configuration"); - - // Assert - response.EnsureSuccessStatusCode(); - Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString()); - var responseBody = await response.Content.ReadAsStreamAsync(); - _ = await JsonSerializer.DeserializeAsync(responseBody); - } - - [Theory] - [InlineData("/Branding/Css")] - [InlineData("/Branding/Css.css")] - public async Task GetCss_ReturnsCorrectResponse(string url) - { - // Arrange - var client = _factory.CreateClient(); - - // Act - var response = await client.GetAsync(url); - - // Assert - response.EnsureSuccessStatusCode(); - Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType?.ToString()); - } - } -} diff --git a/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs new file mode 100644 index 0000000000..3207a0f257 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs @@ -0,0 +1,50 @@ +using System.Text.Json; +using System.Threading.Tasks; +using MediaBrowser.Model.Branding; +using Xunit; + +namespace Jellyfin.Api.Tests +{ + public sealed class BrandingControllerTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + + public BrandingControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetConfiguration_ReturnsCorrectResponse() + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var response = await client.GetAsync("/Branding/Configuration"); + + // Assert + Assert.True(response.IsSuccessStatusCode); + Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType); + Assert.Equal("utf-8", response.Content.Headers.ContentType?.CharSet); + var responseBody = await response.Content.ReadAsStreamAsync(); + _ = await JsonSerializer.DeserializeAsync(responseBody); + } + + [Theory] + [InlineData("/Branding/Css")] + [InlineData("/Branding/Css.css")] + public async Task GetCss_ReturnsCorrectResponse(string url) + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var response = await client.GetAsync(url); + + // Assert + Assert.True(response.IsSuccessStatusCode); + Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType?.ToString()); + } + } +} diff --git a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs new file mode 100644 index 0000000000..fadeddba55 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs @@ -0,0 +1,76 @@ +using System.IO; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models; +using Xunit; + +namespace Jellyfin.Api.Tests.Controllers +{ + public sealed class DashboardControllerTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + + public DashboardControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetDashboardConfigurationPage_NonExistingPage_NotFound() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("web/ConfigurationPage/ThisPageTotally/Doesnt/Exists.html").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetDashboardConfigurationPage_ExistingPage_CorrectPage() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + Assert.Equal("text/html", response.Content.Headers.ContentType?.MediaType); + StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Api.Tests.TestPage.html")!); + Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd()); + } + + [Fact] + public async Task GetDashboardConfigurationPage_BrokenPage_NotFound() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetConfigurationPages_NoParams_AllConfigurationPages() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + var res = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync()); + // TODO: check content + } + + [Fact] + public async Task GetConfigurationPages_True_MainMenuConfigurationPages() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + var res = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync()); + Assert.Empty(res); + } + } +} diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index eca3df79b3..52dd8ae9c1 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -41,4 +41,8 @@ ../jellyfin-tests.ruleset + + + + diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs index 54f8eb225f..dbbd5ac28c 100644 --- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs @@ -73,7 +73,7 @@ namespace Jellyfin.Api.Tests _disposableComponents.Add(loggerFactory); // Create the app host and initialize it - var appHost = new CoreAppHost( + var appHost = new TestAppHost( appPaths, loggerFactory, commandLineOpts, @@ -93,7 +93,7 @@ namespace Jellyfin.Api.Tests var testServer = base.CreateServer(builder); // Finish initializing the app host - var appHost = (CoreAppHost)testServer.Services.GetRequiredService(); + var appHost = (TestAppHost)testServer.Services.GetRequiredService(); appHost.ServiceProvider = testServer.Services; appHost.InitializeServices().GetAwaiter().GetResult(); appHost.RunStartupTasksAsync().GetAwaiter().GetResult(); diff --git a/tests/Jellyfin.Api.Tests/TestAppHost.cs b/tests/Jellyfin.Api.Tests/TestAppHost.cs new file mode 100644 index 0000000000..772e98d049 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/TestAppHost.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Reflection; +using Emby.Server.Implementations; +using Jellyfin.Server; +using MediaBrowser.Controller; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Tests +{ + /// + /// Implementation of the abstract class. + /// + public class TestAppHost : CoreAppHost + { + /// + /// Initializes a new instance of the class. + /// + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + public TestAppHost( + IServerApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + IStartupOptions options, + IFileSystem fileSystem, + IServiceCollection collection) + : base( + applicationPaths, + loggerFactory, + options, + fileSystem, + collection) + { + } + + /// + protected override IEnumerable GetAssembliesWithPartsInternal() + { + foreach (var a in base.GetAssembliesWithPartsInternal()) + { + yield return a; + } + + yield return typeof(TestPlugin).Assembly; + } + } +} diff --git a/tests/Jellyfin.Api.Tests/TestPage.html b/tests/Jellyfin.Api.Tests/TestPage.html new file mode 100644 index 0000000000..8037af8a60 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/TestPage.html @@ -0,0 +1,9 @@ + + + + TestPlugin + + +

This is a Test Page.

+ + diff --git a/tests/Jellyfin.Api.Tests/TestPlugin.cs b/tests/Jellyfin.Api.Tests/TestPlugin.cs new file mode 100644 index 0000000000..a3b4b6994f --- /dev/null +++ b/tests/Jellyfin.Api.Tests/TestPlugin.cs @@ -0,0 +1,43 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace Jellyfin.Api.Tests +{ + public class TestPlugin : BasePlugin, IHasWebPages + { + public TestPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public static TestPlugin? Instance { get; private set; } + + public override Guid Id => new Guid("2d350a13-0bf7-4b61-859c-d5e601b5facf"); + + public override string Name => nameof(TestPlugin); + + public override string Description => "Server test Plugin."; + + public IEnumerable GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".TestPage.html" + }; + + yield return new PluginPageInfo + { + Name = "BrokenPage", + EmbeddedResourcePath = GetType().Namespace + ".foobar" + }; + } + } +} -- cgit v1.2.3 From ec3237ba55a6c0c6e7a31e2aaa5fbf77c9978ac7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 22 Feb 2021 13:46:40 +0100 Subject: Fix tests --- Jellyfin.Api/Controllers/DashboardController.cs | 1 - Jellyfin.Api/Models/ConfigurationPageInfo.cs | 8 ++++++++ tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs | 9 ++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index 0c19f28183..a2c2ecd666 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -6,7 +6,6 @@ using System.Net.Mime; using Jellyfin.Api.Attributes; using Jellyfin.Api.Models; using MediaBrowser.Common.Plugins; -using MediaBrowser.Controller; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using Microsoft.AspNetCore.Http; diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index d214280292..ec4a0d1a1c 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -24,6 +24,14 @@ namespace Jellyfin.Api.Models PluginId = plugin?.Id; } + /// + /// Initializes a new instance of the class. + /// + public ConfigurationPageInfo() + { + Name = string.Empty; + } + /// /// Gets or sets the name. /// diff --git a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs index fadeddba55..84d362a397 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs @@ -3,6 +3,7 @@ using System.Net; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models; +using MediaBrowser.Common.Json; using Xunit; namespace Jellyfin.Api.Tests.Controllers @@ -10,6 +11,7 @@ namespace Jellyfin.Api.Tests.Controllers public sealed class DashboardControllerTests : IClassFixture { private readonly JellyfinApplicationFactory _factory; + private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.GetOptions(); public DashboardControllerTests(JellyfinApplicationFactory factory) { @@ -57,7 +59,6 @@ namespace Jellyfin.Api.Tests.Controllers var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false); Assert.True(response.IsSuccessStatusCode); - var res = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync()); // TODO: check content } @@ -69,8 +70,10 @@ namespace Jellyfin.Api.Tests.Controllers var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false); Assert.True(response.IsSuccessStatusCode); - var res = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync()); - Assert.Empty(res); + var res = await response.Content.ReadAsStreamAsync(); + System.Console.WriteLine(res); + var data = await JsonSerializer.DeserializeAsync(res, _jsonOpions); + Assert.Empty(data); } } } -- cgit v1.2.3 From aff0aea60fc52a2253f04749f11bcb02f6e1f67c Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 23 Feb 2021 14:14:02 +0100 Subject: Improve branch coverage --- Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs | 1 - .../JsonOmdbNotAvailableInt32Converter.cs | 2 +- tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs | 27 +++++++++++ .../Json/JsonCommaDelimitedArrayTests.cs | 52 +++++++++++++++++++++- .../Json/JsonOmdbConverterTests.cs | 27 ++++++----- 5 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs index 588ce717c8..8913180e48 100644 --- a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs +++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Common.Json.Converters; diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs index cb3d83f584..3d97a9de55 100644 --- a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Json.Converters return (int?)converter.ConvertFromString(str); } - return JsonSerializer.Deserialize(ref reader, options); + return JsonSerializer.Deserialize(ref reader, options); } /// diff --git a/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs b/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs new file mode 100644 index 0000000000..2d2f78a981 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using System; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace Jellyfin.Api.Tests +{ + public class TestPluginWithoutPages : BasePlugin + { + public TestPluginWithoutPages(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public static TestPluginWithoutPages? Instance { get; private set; } + + public override Guid Id => new Guid("ae95cbe6-bd3d-4d73-8596-490db334611e"); + + public override string Name => nameof(TestPluginWithoutPages); + + public override string Description => "Server test Plugin without web pages."; + } +} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs index 0d2bdd1af9..ca300401da 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System; +using System.Text.Json; using System.Text.Json.Serialization; using Jellyfin.Common.Tests.Models; using MediaBrowser.Model.Session; @@ -8,6 +9,27 @@ namespace Jellyfin.Common.Tests.Json { public static class JsonCommaDelimitedArrayTests { + [Fact] + public static void Deserialize_String_Null_Success() + { + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": null }", options); + Assert.Null(value?.Value); + } + + [Fact] + public static void Deserialize_Empty_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = Array.Empty() + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": """" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + [Fact] public static void Deserialize_String_Valid_Success() { @@ -48,6 +70,34 @@ namespace Jellyfin.Common.Tests.Json Assert.Equal(desiredValue.Value, value?.Value); } + [Fact] + public static void Deserialize_GenericCommandType_EmptyEntry_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Invalid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + [Fact] public static void Deserialize_GenericCommandType_Space_Valid_Success() { diff --git a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs index faed086a12..efe8063a07 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs @@ -38,6 +38,15 @@ namespace Jellyfin.Common.Tests.Json Assert.Null(result); } + [Theory] + [InlineData("\"8\"", 8)] + [InlineData("8", 8)] + public void Deserialize_NullableInt_Success(string input, int? expected) + { + var result = JsonSerializer.Deserialize(input, _options); + Assert.Equal(result, expected); + } + [Theory] [InlineData("\"N/A\"")] [InlineData("null")] @@ -48,21 +57,11 @@ namespace Jellyfin.Common.Tests.Json } [Theory] - [InlineData("\"8\"", 8)] - [InlineData("8", 8)] - public void Deserialize_Int_Success(string input, int expected) - { - var result = JsonSerializer.Deserialize(input, _options); - Assert.Equal(result, expected); - } - - [Fact] - public void Deserialize_Normal_String_Success() + [InlineData("\"Jellyfin\"", "Jellyfin")] + public void Deserialize_Normal_String_Success(string input, string expected) { - const string Input = "\"Jellyfin\""; - const string Expected = "Jellyfin"; - var result = JsonSerializer.Deserialize(Input, _options); - Assert.Equal(Expected, result); + var result = JsonSerializer.Deserialize(input, _options); + Assert.Equal(expected, result); } [Fact] -- cgit v1.2.3 From d0a2d00b29d87e7f71e85efd38d2b6c8beafe4ea Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 5 Mar 2021 16:56:21 +0100 Subject: Fix UpdateMediaPath model binding (#5378) --- .../Controllers/LibraryStructureController.cs | 13 +++++------- .../UpdateMediaPathRequestDto.cs | 23 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index 328efea26e..be9127dd39 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -241,23 +241,20 @@ namespace Jellyfin.Api.Controllers /// /// Updates a media path. /// - /// The name of the library. - /// The path info. + /// The name of the library and path infos. /// A . /// Media path updated. /// The name of the library may not be empty. [HttpPost("Paths/Update")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdateMediaPath( - [FromQuery] string? name, - [FromBody] MediaPathInfo? pathInfo) + public ActionResult UpdateMediaPath([FromBody, Required] UpdateMediaPathRequestDto mediaPathRequestDto) { - if (string.IsNullOrWhiteSpace(name)) + if (string.IsNullOrWhiteSpace(mediaPathRequestDto.Name)) { - throw new ArgumentNullException(nameof(name)); + throw new ArgumentNullException(nameof(mediaPathRequestDto), "Name must not be null or empty"); } - _libraryManager.UpdateMediaPath(name, pathInfo); + _libraryManager.UpdateMediaPath(mediaPathRequestDto.Name, mediaPathRequestDto.PathInfo); return NoContent(); } diff --git a/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs b/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs new file mode 100644 index 0000000000..fbd4985f9c --- /dev/null +++ b/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Api.Models.LibraryStructureDto +{ + /// + /// Update library options dto. + /// + public class UpdateMediaPathRequestDto + { + /// + /// Gets or sets the library name. + /// + [Required] + public string Name { get; set; } = null!; + + /// + /// Gets or sets library folder path information. + /// + [Required] + public MediaPathInfo PathInfo { get; set; } = null!; + } +} -- cgit v1.2.3 From eef15dc7ac23f026f11334e2a262a572df126e48 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 11 Mar 2021 22:45:58 -0700 Subject: Fix third part integration --- Jellyfin.Api/Controllers/LibraryController.cs | 6 ++--- .../Controllers/NotificationsController.cs | 21 +++++---------- .../Models/LibraryDtos/MediaUpdateInfoDto.cs | 15 +++++------ .../Models/LibraryDtos/MediaUpdateInfoPathDto.cs | 19 ++++++++++++++ .../NotificationDtos/AdminNotificationDto.cs | 30 ++++++++++++++++++++++ 5 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs create mode 100644 Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 3443ebd72f..2bd2d4933d 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -590,15 +590,15 @@ namespace Jellyfin.Api.Controllers /// /// Reports that new movies have been added by an external source. /// - /// A list of updated media paths. + /// The update paths. /// Report success. /// A . [HttpPost("Library/Media/Updated")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates) + public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto) { - foreach (var item in updates) + foreach (var item in dto.Updates) { _libraryMonitor.ReportFileSystemChanged(item.Path); } diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 0ceda6815c..420630cdf4 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using Jellyfin.Api.Constants; @@ -86,26 +87,19 @@ namespace Jellyfin.Api.Controllers /// /// Sends a notification to all admins. /// - /// The URL of the notification. - /// The level of the notification. - /// The name of the notification. - /// The description of the notification. + /// The notification request. /// Notification sent. /// A . [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult CreateAdminNotification( - [FromQuery] string? url, - [FromQuery] NotificationLevel? level, - [FromQuery] string name = "", - [FromQuery] string description = "") + public ActionResult CreateAdminNotification([FromBody, Required] AdminNotificationDto notificationDto) { var notification = new NotificationRequest { - Name = name, - Description = description, - Url = url, - Level = level ?? NotificationLevel.Normal, + Name = notificationDto.Name, + Description = notificationDto.Description, + Url = notificationDto.Url, + Level = notificationDto.NotificationLevel ?? NotificationLevel.Normal, UserIds = _userManager.Users .Where(user => user.HasPermission(PermissionKind.IsAdministrator)) .Select(user => user.Id) @@ -114,7 +108,6 @@ namespace Jellyfin.Api.Controllers }; _notificationManager.SendNotification(notification, CancellationToken.None); - return NoContent(); } diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs index 991dbfc502..f936388980 100644 --- a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs +++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs @@ -1,4 +1,7 @@ -namespace Jellyfin.Api.Models.LibraryDtos +using System; +using System.Collections.Generic; + +namespace Jellyfin.Api.Models.LibraryDtos { /// /// Media Update Info Dto. @@ -6,14 +9,8 @@ public class MediaUpdateInfoDto { /// - /// Gets or sets media path. - /// - public string? Path { get; set; } - - /// - /// Gets or sets media update type. - /// Created, Modified, Deleted. + /// Gets or sets the list of updates. /// - public string? UpdateType { get; set; } + public IReadOnlyList Updates { get; set; } = Array.Empty(); } } diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs new file mode 100644 index 0000000000..852315b92d --- /dev/null +++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs @@ -0,0 +1,19 @@ +namespace Jellyfin.Api.Models.LibraryDtos +{ + /// + /// The media update info path. + /// + public class MediaUpdateInfoPathDto + { + /// + /// Gets or sets media path. + /// + public string? Path { get; set; } + + /// + /// Gets or sets media update type. + /// Created, Modified, Deleted. + /// + public string? UpdateType { get; set; } + } +} diff --git a/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs b/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs new file mode 100644 index 0000000000..2c3a6282f2 --- /dev/null +++ b/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs @@ -0,0 +1,30 @@ +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Api.Models.NotificationDtos +{ + /// + /// The admin notification dto. + /// + public class AdminNotificationDto + { + /// + /// Gets or sets the notification name. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the notification description. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the notification level. + /// + public NotificationLevel? NotificationLevel { get; set; } + + /// + /// Gets or sets the notification url. + /// + public string? Url { get; set; } + } +} -- cgit v1.2.3 From 5b0dc21c644af074095b760b2e42c516ce07c0d6 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 21 Apr 2021 20:46:15 -0600 Subject: Mark password property as obsolete --- Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Api/Models') diff --git a/Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs b/Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs index 3936274356..41f7b169eb 100644 --- a/Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs +++ b/Jellyfin.Api/Models/UserDtos/AuthenticateUserByName.cs @@ -1,4 +1,6 @@ -namespace Jellyfin.Api.Models.UserDtos +using System; + +namespace Jellyfin.Api.Models.UserDtos { /// /// The authenticate user by name request body. @@ -18,6 +20,7 @@ /// /// Gets or sets the sha1-hashed password. /// + [Obsolete("Send password using pw field")] public string? Password { get; set; } } } -- cgit v1.2.3 From 7e8428e588b3f0a0574da44081098c64fe1a47d7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 20 May 2021 21:28:18 +0200 Subject: Enable nullable reference types for Emby.Server.Implementations --- .../AppBase/BaseApplicationPaths.cs | 8 ++------ .../AppBase/BaseConfigurationManager.cs | 2 ++ .../AppBase/ConfigurationHelper.cs | 2 -- Emby.Server.Implementations/ApplicationHost.cs | 2 ++ .../Channels/ChannelManager.cs | 2 ++ .../Collections/CollectionImageProvider.cs | 4 ++-- .../Collections/CollectionManager.cs | 2 ++ .../Configuration/ServerConfigurationManager.cs | 2 ++ .../Cryptography/CryptographyProvider.cs | 2 -- .../Data/BaseSqliteRepository.cs | 2 ++ .../Data/ManagedConnection.cs | 2 +- Emby.Server.Implementations/Data/SqliteExtensions.cs | 1 + .../Data/SqliteItemRepository.cs | 2 ++ .../Data/SqliteUserDataRepository.cs | 2 ++ Emby.Server.Implementations/Data/TypeMapper.cs | 6 +++--- Emby.Server.Implementations/Devices/DeviceId.cs | 2 ++ Emby.Server.Implementations/Devices/DeviceManager.cs | 2 ++ Emby.Server.Implementations/Dto/DtoService.cs | 2 ++ .../Emby.Server.Implementations.csproj | 1 + .../EntryPoints/ExternalPortForwarding.cs | 2 ++ .../EntryPoints/LibraryChangedNotifier.cs | 2 ++ .../EntryPoints/RecordingNotifier.cs | 2 ++ .../EntryPoints/UdpServerEntryPoint.cs | 2 -- .../EntryPoints/UserDataChangeNotifier.cs | 2 ++ .../HttpServer/Security/AuthorizationContext.cs | 20 ++++++++++---------- .../HttpServer/Security/SessionContext.cs | 4 ++-- .../HttpServer/WebSocketConnection.cs | 2 -- .../HttpServer/WebSocketManager.cs | 2 ++ Emby.Server.Implementations/IO/FileRefresher.cs | 2 ++ Emby.Server.Implementations/IO/LibraryMonitor.cs | 2 ++ Emby.Server.Implementations/IO/ManagedFileSystem.cs | 6 +++--- .../IO/MbLinkShortcutHandler.cs | 2 +- Emby.Server.Implementations/IO/StreamHelper.cs | 2 +- Emby.Server.Implementations/IStartupOptions.cs | 1 - .../Images/BaseDynamicImageProvider.cs | 2 ++ .../Images/CollectionFolderImageProvider.cs | 2 ++ .../Images/DynamicImageProvider.cs | 2 ++ .../Images/FolderImageProvider.cs | 2 ++ .../Images/GenreImageProvider.cs | 2 ++ .../Images/PlaylistImageProvider.cs | 2 ++ .../Library/ExclusiveLiveStream.cs | 2 ++ .../Library/IgnorePatterns.cs | 2 -- .../Library/LibraryManager.cs | 2 ++ .../Library/LiveStreamHelper.cs | 2 ++ .../Library/MediaSourceManager.cs | 2 ++ .../Library/MediaStreamSelector.cs | 2 ++ Emby.Server.Implementations/Library/MusicManager.cs | 2 ++ .../Library/PathExtensions.cs | 2 -- .../Library/ResolverHelper.cs | 2 -- .../Library/Resolvers/Audio/AudioResolver.cs | 2 ++ .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 2 ++ .../Library/Resolvers/Audio/MusicArtistResolver.cs | 2 ++ .../Library/Resolvers/BaseVideoResolver.cs | 2 ++ .../Library/Resolvers/Books/BookResolver.cs | 2 ++ .../Library/Resolvers/FolderResolver.cs | 2 ++ .../Library/Resolvers/ItemResolver.cs | 2 ++ .../Library/Resolvers/Movies/BoxSetResolver.cs | 2 ++ .../Library/Resolvers/Movies/MovieResolver.cs | 2 ++ .../Library/Resolvers/PhotoAlbumResolver.cs | 2 ++ .../Library/Resolvers/PhotoResolver.cs | 2 ++ .../Library/Resolvers/PlaylistResolver.cs | 2 ++ .../Library/Resolvers/SpecialFolderResolver.cs | 2 ++ .../Library/Resolvers/TV/EpisodeResolver.cs | 2 ++ .../Library/Resolvers/TV/SeasonResolver.cs | 2 ++ .../Library/Resolvers/TV/SeriesResolver.cs | 2 ++ .../Library/Resolvers/VideoResolver.cs | 2 ++ Emby.Server.Implementations/Library/SearchEngine.cs | 2 ++ .../Library/UserDataManager.cs | 2 ++ .../Library/UserViewManager.cs | 2 ++ .../LiveTv/EmbyTV/DirectRecorder.cs | 2 ++ Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 2 ++ .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 ++ .../LiveTv/EmbyTV/EpgChannelData.cs | 7 +++---- .../LiveTv/EmbyTV/ItemDataProvider.cs | 2 ++ .../LiveTv/EmbyTV/RecordingHelper.cs | 2 ++ .../LiveTv/EmbyTV/TimerManager.cs | 2 ++ .../LiveTv/Listings/SchedulesDirect.cs | 2 ++ .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 ++ .../LiveTv/LiveTvDtoService.cs | 2 ++ Emby.Server.Implementations/LiveTv/LiveTvManager.cs | 2 ++ .../LiveTv/LiveTvMediaSourceProvider.cs | 2 ++ .../LiveTv/TunerHosts/BaseTunerHost.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/Channels.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs | 2 ++ .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 2 ++ .../LiveTv/TunerHosts/LiveStream.cs | 2 ++ .../LiveTv/TunerHosts/M3UTunerHost.cs | 2 ++ .../LiveTv/TunerHosts/M3uParser.cs | 2 ++ .../LiveTv/TunerHosts/SharedHttpStream.cs | 2 ++ .../Localization/LocalizationManager.cs | 2 ++ .../MediaEncoder/EncodingManager.cs | 2 ++ Emby.Server.Implementations/Net/SocketFactory.cs | 2 ++ Emby.Server.Implementations/Net/UdpSocket.cs | 2 ++ .../Playlists/PlaylistManager.cs | 2 ++ Emby.Server.Implementations/Plugins/PluginManager.cs | 2 -- .../QuickConnect/QuickConnectManager.cs | 2 ++ .../ScheduledTasks/ScheduledTaskWorker.cs | 2 ++ .../ScheduledTasks/TaskManager.cs | 2 ++ .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 6 ++++-- .../ScheduledTasks/Tasks/CleanActivityLogTask.cs | 4 ++-- .../ScheduledTasks/Triggers/DailyTrigger.cs | 2 ++ .../ScheduledTasks/Triggers/IntervalTrigger.cs | 2 ++ .../ScheduledTasks/Triggers/StartupTrigger.cs | 2 ++ .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 2 ++ .../Security/AuthenticationRepository.cs | 2 ++ .../Serialization/MyXmlSerializer.cs | 10 ++++++---- .../Session/SessionManager.cs | 2 ++ .../Session/SessionWebSocketListener.cs | 2 ++ .../Session/WebSocketController.cs | 1 - .../Sorting/AiredEpisodeOrderComparer.cs | 2 +- .../Sorting/AlbumArtistComparer.cs | 4 ++-- Emby.Server.Implementations/Sorting/AlbumComparer.cs | 4 ++-- .../Sorting/ArtistComparer.cs | 4 ++-- .../Sorting/CommunityRatingComparer.cs | 2 +- .../Sorting/CriticRatingComparer.cs | 6 +++--- .../Sorting/DateCreatedComparer.cs | 2 +- .../Sorting/DateLastMediaAddedComparer.cs | 1 + .../Sorting/DatePlayedComparer.cs | 2 ++ .../Sorting/IsFavoriteOrLikeComparer.cs | 1 + .../Sorting/IsFolderComparer.cs | 6 +++--- .../Sorting/IsPlayedComparer.cs | 2 ++ .../Sorting/IsUnplayedComparer.cs | 2 ++ Emby.Server.Implementations/Sorting/NameComparer.cs | 2 +- .../Sorting/OfficialRatingComparer.cs | 2 +- .../Sorting/PlayCountComparer.cs | 2 ++ .../Sorting/PremiereDateComparer.cs | 9 +++++++-- .../Sorting/ProductionYearComparer.cs | 9 +++++++-- .../Sorting/RandomComparer.cs | 2 +- .../Sorting/RuntimeComparer.cs | 2 ++ .../Sorting/SeriesSortNameComparer.cs | 2 ++ .../Sorting/SortNameComparer.cs | 2 ++ .../Sorting/StartDateComparer.cs | 2 ++ .../Sorting/StudioComparer.cs | 2 ++ Emby.Server.Implementations/SyncPlay/Group.cs | 2 ++ .../SyncPlay/SyncPlayManager.cs | 2 ++ Emby.Server.Implementations/TV/TVSeriesManager.cs | 2 ++ Emby.Server.Implementations/Udp/UdpServer.cs | 2 ++ .../Updates/InstallationManager.cs | 2 ++ Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 ++-- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 4 +++- .../Models/PlaybackDtos/TranscodingThrottler.cs | 3 ++- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 11 +++++++---- .../Extensions/StringExtensions.cs | 1 + MediaBrowser.Controller/Net/ISessionContext.cs | 4 ++-- MediaBrowser.Controller/Sorting/IBaseItemComparer.cs | 2 +- MediaBrowser.Model/IO/IFileSystem.cs | 7 +++---- MediaBrowser.Model/IO/IShortcutHandler.cs | 2 +- MediaBrowser.Model/IO/IStreamHelper.cs | 2 +- MediaBrowser.Model/Tasks/ITaskTrigger.cs | 2 +- 151 files changed, 300 insertions(+), 99 deletions(-) (limited to 'Jellyfin.Api/Models') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 660bbb2deb..6edfad575a 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase CachePath = cacheDirectoryPath; WebPath = webDirectoryPath; - DataPath = Path.Combine(ProgramDataPath, "data"); + _dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName; } /// @@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase /// Gets the folder path to the data directory. /// /// The data directory. - public string DataPath - { - get => _dataPath; - private set => _dataPath = Directory.CreateDirectory(value).FullName; - } + public string DataPath => _dataPath; /// public string VirtualDataPath => "%AppDataPath%"; diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 4f72c8ce15..8c919db431 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 29bac66340..de770f59ec 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.IO; using System.Linq; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 75d8fc113d..82995deb30 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 7324b0ee9f..448f124034 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index c69a07e83e..ca84094024 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections return null; }) .Where(i => i != null) - .GroupBy(x => x.Id) + .GroupBy(x => x!.Id) // We removed the null values .Select(x => x.First()) - .ToList(); + .ToList()!; // Again... the list doesn't contain any null values } /// diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index c56f334481..82d80fc83c 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 7a8ed8c29f..ff5602f243 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Globalization; using System.IO; diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 12a9e44e70..4a9b280852 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Security.Cryptography; diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index c331a61124..6f23a0888e 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 5c094ddd2d..10c6f837e6 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data { public class ManagedConnection : IDisposable { - private SQLiteDatabaseConnection _db; + private SQLiteDatabaseConnection? _db; private readonly SemaphoreSlim _writeLock; private bool _disposed = false; diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index a8f3feb589..e532825af5 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index b3d8860a9e..5b4bbb3395 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index e0ebd0a6cf..1756bcae08 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/TypeMapper.cs b/Emby.Server.Implementations/Data/TypeMapper.cs index 7044b1d194..7f1306d15a 100644 --- a/Emby.Server.Implementations/Data/TypeMapper.cs +++ b/Emby.Server.Implementations/Data/TypeMapper.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data /// This holds all the types in the running assemblies /// so that we can de-serialize properly when we don't have strong types. /// - private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary(); /// /// Gets the type. @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Data /// Name of the type. /// Type. /// typeName is null. - public Type GetType(string typeName) + public Type? GetType(string typeName) { if (string.IsNullOrEmpty(typeName)) { @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Data /// /// Name of the type. /// Type. - private Type LookupType(string typeName) + private Type? LookupType(string typeName) { return AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(typeName)) diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index fa6ac95fd3..3d15b3e768 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index da5047d244..2637addce7 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 4ae35039ab..7411239a1e 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 14f6f565c4..113863519b 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -44,6 +44,7 @@ false true true + enable AD0001 AllEnabledByDefault diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 14201ead29..cc3e4a2c24 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index ae1b51b4c3..5bb4100ba9 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 824bb85f44..e0ca02d986 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 3624e079f5..211941f443 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Net.Sockets; using System.Threading; diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 1989e9ed25..332fb33858 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index fbf9254d16..c87f7dbbde 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached)) { - return (AuthorizationInfo)cached; + return (AuthorizationInfo)cached!; // Cache should never contain null } return GetAuthorization(requestContext); @@ -55,15 +55,15 @@ namespace Emby.Server.Implementations.HttpServer.Security } private AuthorizationInfo GetAuthorizationInfoFromDictionary( - in Dictionary auth, + in Dictionary? auth, in IHeaderDictionary headers, in IQueryCollection queryString) { - string deviceId = null; - string device = null; - string client = null; - string version = null; - string token = null; + string? deviceId = null; + string? device = null; + string? client = null; + string? version = null; + string? token = null; if (auth != null) { @@ -206,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorizationDictionary(HttpContext httpReq) + private Dictionary? GetAuthorizationDictionary(HttpContext httpReq) { var auth = httpReq.Request.Headers["X-Emby-Authorization"]; @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security ///
/// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorizationDictionary(HttpRequest httpReq) + private Dictionary? GetAuthorizationDictionary(HttpRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -240,7 +240,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The authorization header. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorization(ReadOnlySpan authorizationHeader) + private Dictionary? GetAuthorization(ReadOnlySpan authorizationHeader) { if (authorizationHeader == null) { diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index dd77b45d89..c375f36ce4 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -36,14 +36,14 @@ namespace Emby.Server.Implementations.HttpServer.Security return GetSession((HttpContext)requestContext); } - public User GetUser(HttpContext requestContext) + public User? GetUser(HttpContext requestContext) { var session = GetSession(requestContext); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public User? GetUser(object requestContext) { return GetUser(((HttpRequest)requestContext).HttpContext); } diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 06acb56061..8f7d606692 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Buffers; using System.IO.Pipelines; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 1bee1ac310..861c0a95e3 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 7435e9d0bf..47a83d77ce 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 3353fae9d8..aa80bccd72 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 27096ed334..6a554e68ab 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.IO /// The filename. /// System.String. /// filename - public virtual string ResolveShortcut(string filename) + public virtual string? ResolveShortcut(string filename) { if (string.IsNullOrEmpty(filename)) { @@ -601,7 +601,7 @@ namespace Emby.Server.Implementations.IO return GetFiles(path, null, false, recursive); } - public virtual IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable GetFiles(string path, IReadOnlyList? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var enumerationOptions = GetEnumerationOptions(recursive); @@ -655,7 +655,7 @@ namespace Emby.Server.Implementations.IO return GetFilePaths(path, null, false, recursive); } - public virtual IEnumerable GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var enumerationOptions = GetEnumerationOptions(recursive); diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs index e6696b8c4c..76c58d5dcd 100644 --- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO public string Extension => ".mblink"; - public string Resolve(string shortcutPath) + public string? Resolve(string shortcutPath) { if (string.IsNullOrEmpty(shortcutPath)) { diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index c16ebd61b7..e4f5f4cf0b 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO { public class StreamHelper : IStreamHelper { - public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) + public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken) { byte[] buffer = ArrayPool.Shared.Rent(bufferSize); try diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index f719dc5f89..a430b9e720 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#nullable enable namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 6fa3c1c618..833fb0b7a1 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 161b4c4528..ff5f26ce09 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 50c5314820..900b3fd9c6 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs index 0224ab32a0..859017f869 100644 --- a/Emby.Server.Implementations/Images/FolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index 3817882312..6da431c68e 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs index a4c106e879..b8f0f0d65c 100644 --- a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 236453e805..6c65b58999 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index e30a675931..5384c04b3b 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Linq; using DotNet.Globbing; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 4d207471a2..f8d8197d46 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index c2951dd155..4ef7923db3 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 85d6d3043e..38e81d14c4 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 28fa062396..b833122ea0 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index f8bae4fd1a..06300adebc 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 0de4edb7e7..86b8039fab 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Diagnostics.CodeAnalysis; using MediaBrowser.Common.Providers; diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 1d9b448741..ac75e5d3a1 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.IO; using System.Linq; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 4ad84579d8..e893d63350 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index bf32381ebf..8e1eccb10a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 60f82806fb..3d2ae95d24 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 16050185fb..a3dcdc9441 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 0525c7e307..68076730b3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs index 7dbce7a6ef..7aaee017dd 100644 --- a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index 92fb2a753a..fa45ccf840 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index 295e9e120b..69d71d0d9a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 16bf4dc4a0..02c5287646 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 204c8a62e7..534bc80ddc 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 3cb6542cf7..57bf40e9ec 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index 5f051321f6..ecd44be477 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 99f3041909..7b4e143348 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 6f29bc6494..d6ae910565 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 768e2e4f5b..7d707df182 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Globalization; using Emby.Naming.TV; using MediaBrowser.Controller.Entities.TV; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 8fc3e3e75c..a1562abd31 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs index 62268fce90..9599faea4b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index bcdf854ca3..26e615fa08 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 827e3c64b3..667e466133 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index ac041bcf6c..e2da672a3c 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 7a6b1d8b61..3fcadf5b1b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 28a2095e16..7970631201 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 9372b0f6c3..26e4ef1ed9 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs index 8c27ca76e3..0ec52a9598 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - internal class EpgChannelData { @@ -39,13 +38,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - public ChannelInfo GetChannelById(string id) + public ChannelInfo? GetChannelById(string id) => _channelsById.GetValueOrDefault(id); - public ChannelInfo GetChannelByNumber(string number) + public ChannelInfo? GetChannelByNumber(string number) => _channelsByNumber.GetValueOrDefault(number); - public ChannelInfo GetChannelByName(string name) + public ChannelInfo? GetChannelByName(string name) => _channelsByName.GetValueOrDefault(name); public static string NormalizeName(string value) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 1cac9cb963..bdab8c3e4b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 32245f899e..1088638691 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 1efa90e256..6c52a9a73d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 9af65cabba..00d02873c5 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 6824aa4423..ebad4eddf1 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 6af49dd45e..21e1409ac0 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1145d8aa1e..1f16289002 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 3a738fd5d2..ecd28097d3 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index fbcd4ef372..00a37bb022 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs index 740cbb66ee..0f04531896 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { internal class Channels diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs index 09d77f8382..42068cd340 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index bbac6e0551..c5700db715 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index a7fda1d72e..3016eeda24 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index b16ccc561a..50a2d9abb3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index f8baf55da7..96a678c1d3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 4b170b2e4e..69035dac96 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 84d4161499..48a0c3cd37 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index eeb2426f4f..137ed27e20 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 220e423bf5..dd5dee1d16 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 031b5d2e72..8aaa1f7bbe 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 0781a0e333..1377286168 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 4e25768cf6..a8b18d2925 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 2d1a559f16..9a1ca99467 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 14df20936c..48281b75f6 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 0259dc436b..7cfd1fced9 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Globalization; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 101d9b5377..ccbd4289ea 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index af316e1083..4f0df75bf8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 2312c85d97..baeb86a221 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -140,8 +140,10 @@ namespace Emby.Server.Implementations.ScheduledTasks previouslyFailedImages.Add(key); var parentPath = Path.GetDirectoryName(failHistoryPath); - - Directory.CreateDirectory(parentPath); + if (parentPath != null) + { + Directory.CreateDirectory(parentPath); + } string text = string.Join('|', previouslyFailedImages); File.WriteAllText(failHistoryPath, text); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 4abbf784b2..50ba9bc899 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -75,4 +75,4 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Enumerable.Empty(); } } -} \ No newline at end of file +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 3b40320ab8..3b63536a42 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index b04fd7c7e2..e13782fe02 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 7cd5493da8..ced14195bb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 0c0ebec082..a67f940b75 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 76f863c951..30823ab8f5 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 27024e4e1c..8d8b82f0a4 100644 --- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -19,7 +19,9 @@ namespace Emby.Server.Implementations.Serialization new ConcurrentDictionary(); private static XmlSerializer GetSerializer(Type type) - => _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type)); + => _serializers.GetOrAdd( + type.FullName ?? throw new ArgumentException($"Invalid type {type}."), + _ => new XmlSerializer(type)); /// /// Serializes to writer. @@ -38,7 +40,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The stream. /// System.Object. - public object DeserializeFromStream(Type type, Stream stream) + public object? DeserializeFromStream(Type type, Stream stream) { using (var reader = XmlReader.Create(stream)) { @@ -81,7 +83,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The file. /// System.Object. - public object DeserializeFromFile(Type type, string file) + public object? DeserializeFromFile(Type type, string file) { using (var stream = File.OpenRead(file)) { @@ -95,7 +97,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The buffer. /// System.Object. - public object DeserializeFromBytes(Type type, byte[] buffer) + public object? DeserializeFromBytes(Type type, byte[] buffer) { using (var stream = new MemoryStream(buffer, 0, buffer.Length, false, true)) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6844152ea5..ef467da7ed 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 39c369a01d..e9e3ca7f4b 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index a653b58c2b..ed1dfca592 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 #pragma warning disable SA1600 -#nullable enable using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 60698e803d..2b0ab536f9 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 7657cc74e1..42e644970c 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as IHasAlbumArtist; diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index 7dfdd9ecff..1db3f5e9ca 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as Audio; diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs index 756d3c5b6c..98bee3fd9f 100644 --- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting public string Name => ItemSortBy.Artist; /// - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { if (!(x is Audio audio)) { diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs index 980954ba03..5f142fa4bb 100644 --- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs index fa136c36d0..d20dedc2d4 100644 --- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -15,14 +15,14 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } - private static float GetValue(BaseItem x) + private static float GetValue(BaseItem? x) { - return x.CriticRating ?? 0; + return x?.CriticRating ?? 0; } /// diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs index cbca300d2f..d3f10f78cb 100644 --- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 03ff19d21c..b1cb123ce1 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 16bd2aff86..08a44319f2 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 0c4e82d011..73e628cf75 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs index a35192eff8..3c5ddeefaa 100644 --- a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -30,9 +30,9 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { - return x.IsFolder ? 0 : 1; + return x?.IsFolder ?? true ? 0 : 1; } } } diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index d95948406f..7d77a8bc5a 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index 1632c5a7a9..926835f906 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index da020d8d8e..4de81a69e3 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index 76bb798b5f..a81f78ebfc 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index 5c28303229..04e4865cbf 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index 92ac04dc66..c98f97bf1e 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetDate(x).CompareTo(GetDate(y)); } @@ -26,8 +26,13 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// DateTime. - private static DateTime GetDate(BaseItem x) + private static DateTime GetDate(BaseItem? x) { + if (x == null) + { + return DateTime.MinValue; + } + if (x.PremiereDate.HasValue) { return x.PremiereDate.Value; diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs index e2857df0b9..df9f9957d6 100644 --- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -25,8 +25,13 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// DateTime. - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { + if (x == null) + { + return 0; + } + if (x.ProductionYear.HasValue) { return x.ProductionYear.Value; diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs index 7739d04182..af3bc27508 100644 --- a/Emby.Server.Implementations/Sorting/RandomComparer.cs +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return Guid.NewGuid().CompareTo(Guid.NewGuid()); } diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs index dde44333d5..1293153031 100644 --- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index b9205ee076..4123a59f8b 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index f745e193b4..8d30716d39 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs index 558a3d3513..c3df7c47e6 100644 --- a/Emby.Server.Implementations/Sorting/StartDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 5766dc542b..01445c5254 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 7c2ad2477a..12efff261b 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 72c0a838e2..993456196d 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 829df64bfb..a837f09cac 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index db5265e79d..750f001680 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 653b1381b9..2351b7d8c6 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable using System; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b4154b361e..45559fce9a 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1762,9 +1762,9 @@ namespace Jellyfin.Api.Controllers private static FileSystemMetadata? GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem) { - var folder = Path.GetDirectoryName(playlist); + var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException("Path can't be a root directory.", nameof(playlist)); - var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty; + var filePrefix = Path.GetFileNameWithoutExtension(playlist); try { diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 0879cbd18f..7cb0159936 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -380,7 +380,9 @@ namespace Jellyfin.Api.Helpers /// The output file path. private void DeleteHlsPartialStreamFiles(string outputFilePath) { - var directory = Path.GetDirectoryName(outputFilePath); + var directory = Path.GetDirectoryName(outputFilePath) + ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath)); + var name = Path.GetFileNameWithoutExtension(outputFilePath); var filesToDelete = _fileSystem.GetFilePaths(directory) diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs index e33e552edb..7b32d76ba7 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs @@ -145,7 +145,8 @@ namespace Jellyfin.Api.Models.PlaybackDtos var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0; - var path = job.Path; + var path = job.Path ?? throw new ArgumentException("Path can't be null."); + var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index a15a381772..96bd2ccc40 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -82,11 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); - var config = File.Exists(Path.Combine(userDataDir, "config.xml")) - ? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml")) + var configPath = Path.Combine(userDataDir, "config.xml"); + var config = File.Exists(configPath) + ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration() : new UserConfiguration(); - var policy = File.Exists(Path.Combine(userDataDir, "policy.xml")) - ? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml")) + + var policyPath = Path.Combine(userDataDir, "policy.xml"); + var policy = File.Exists(policyPath) + ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy() : new UserPolicy(); policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace( "Emby.Server.Implementations.Library", diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index 48bd9522a8..1853896eec 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index a60dc2ea19..6b896b41f4 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -10,10 +10,10 @@ namespace MediaBrowser.Controller.Net { SessionInfo GetSession(object requestContext); - User GetUser(object requestContext); + User? GetUser(object requestContext); SessionInfo GetSession(HttpContext requestContext); - User GetUser(HttpContext requestContext); + User? GetUser(HttpContext requestContext); } } diff --git a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs index 727cbe639c..07fe1ea8a9 100644 --- a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Sorting /// /// Interface IBaseItemComparer. /// - public interface IBaseItemComparer : IComparer + public interface IBaseItemComparer : IComparer { /// /// Gets the name. diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index e5c26430a8..be4f1e16b5 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -25,7 +24,7 @@ namespace MediaBrowser.Model.IO /// /// The filename. /// System.String. - string ResolveShortcut(string filename); + string? ResolveShortcut(string filename); /// /// Creates the shortcut. @@ -160,7 +159,7 @@ namespace MediaBrowser.Model.IO /// All found files. IEnumerable GetFiles(string path, bool recursive = false); - IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable GetFiles(string path, IReadOnlyList? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// /// Gets the file system entries. @@ -186,7 +185,7 @@ namespace MediaBrowser.Model.IO /// IEnumerable<System.String>. IEnumerable GetFilePaths(string path, bool recursive = false); - IEnumerable GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// /// Gets the file system entry paths. diff --git a/MediaBrowser.Model/IO/IShortcutHandler.cs b/MediaBrowser.Model/IO/IShortcutHandler.cs index 14d5c4b62f..2c364a962f 100644 --- a/MediaBrowser.Model/IO/IShortcutHandler.cs +++ b/MediaBrowser.Model/IO/IShortcutHandler.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.IO /// /// The shortcut path. /// System.String. - string Resolve(string shortcutPath); + string? Resolve(string shortcutPath); /// /// Creates the specified shortcut path. diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs index 0e09db16e8..f900da5567 100644 --- a/MediaBrowser.Model/IO/IStreamHelper.cs +++ b/MediaBrowser.Model/IO/IStreamHelper.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Model.IO { public interface IStreamHelper { - Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken); + Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken); Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs index cbd60cca18..db9fba6964 100644 --- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs +++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Tasks /// /// Fires when the trigger condition is satisfied and the task should run. /// - event EventHandler Triggered; + event EventHandler? Triggered; /// /// Gets or sets the options of this task. -- cgit v1.2.3