Browse Source

Merge branch 'master' into bug/authorization-header-issue

cvium 3 years ago
parent
commit
6b3ecf2533
100 changed files with 2027 additions and 1790 deletions
  1. 3 0
      .gitignore
  2. 1 1
      Emby.Dlna/ContentDirectory/ServerItem.cs
  3. 1 1
      Emby.Dlna/Didl/DidlBuilder.cs
  4. 3 1
      Emby.Dlna/PlayTo/PlayToManager.cs
  5. 108 151
      Emby.Server.Implementations/ApplicationHost.cs
  6. 3 3
      Emby.Server.Implementations/Channels/ChannelManager.cs
  7. 3 3
      Emby.Server.Implementations/Collections/CollectionManager.cs
  8. 1 1
      Emby.Server.Implementations/Data/BaseSqliteRepository.cs
  9. 21 16
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  10. 26 27
      Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
  11. 0 146
      Emby.Server.Implementations/Devices/DeviceManager.cs
  12. 25 28
      Emby.Server.Implementations/Dto/DtoService.cs
  13. 2 2
      Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  14. 3 0
      Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
  15. 3 2
      Emby.Server.Implementations/HttpServer/Security/AuthService.cs
  16. 14 7
      Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
  17. 2 2
      Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
  18. 1 1
      Emby.Server.Implementations/HttpServer/WebSocketManager.cs
  19. 3 3
      Emby.Server.Implementations/IO/ManagedFileSystem.cs
  20. 1 1
      Emby.Server.Implementations/IStartupOptions.cs
  21. 1 1
      Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
  22. 6 6
      Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
  23. 15 20
      Emby.Server.Implementations/Library/LibraryManager.cs
  24. 3 2
      Emby.Server.Implementations/Library/MusicManager.cs
  25. 6 6
      Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
  26. 9 21
      Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
  27. 0 0
      Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
  28. 1 1
      Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
  29. 2 2
      Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  30. 2 1
      Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
  31. 3 1
      Emby.Server.Implementations/Library/UserDataManager.cs
  32. 8 5
      Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
  33. 3 5
      Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
  34. 33 40
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  35. 0 5
      Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
  36. 0 1
      Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
  37. 1 1
      Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
  38. 5 7
      Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
  39. 113 509
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  40. 36 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
  41. 24 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
  42. 48 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
  43. 31 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
  44. 24 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
  45. 42 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
  46. 39 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
  47. 24 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
  48. 24 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
  49. 25 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
  50. 18 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
  51. 24 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
  52. 37 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
  53. 72 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
  54. 42 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
  55. 37 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
  56. 36 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
  57. 48 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
  58. 30 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
  59. 18 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
  60. 42 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
  61. 31 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
  62. 24 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
  63. 157 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
  64. 91 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
  65. 42 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
  66. 24 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
  67. 24 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
  68. 25 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
  69. 25 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
  70. 67 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
  71. 18 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
  72. 36 0
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
  73. 28 33
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  74. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
  75. 30 34
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  76. 1 0
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
  77. 11 11
      Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  78. 2 2
      Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
  79. 2 2
      Emby.Server.Implementations/Localization/Core/bg-BG.json
  80. 1 1
      Emby.Server.Implementations/Localization/Core/tr.json
  81. 6 14
      Emby.Server.Implementations/Net/SocketFactory.cs
  82. 2 2
      Emby.Server.Implementations/Net/UdpSocket.cs
  83. 2 2
      Emby.Server.Implementations/Playlists/PlaylistManager.cs
  84. 1 0
      Emby.Server.Implementations/Properties/AssemblyInfo.cs
  85. 79 34
      Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
  86. 1 2
      Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  87. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs
  88. 4 0
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  89. 0 408
      Emby.Server.Implementations/Security/AuthenticationRepository.cs
  90. 68 120
      Emby.Server.Implementations/Session/SessionManager.cs
  91. 2 2
      Emby.Server.Implementations/Session/SessionWebSocketListener.cs
  92. 1 1
      Emby.Server.Implementations/Sorting/ArtistComparer.cs
  93. 11 11
      Emby.Server.Implementations/TV/TVSeriesManager.cs
  94. 5 5
      Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
  95. 1 1
      Jellyfin.Api/Controllers/ActivityLogController.cs
  96. 18 34
      Jellyfin.Api/Controllers/ApiKeyController.cs
  97. 1 1
      Jellyfin.Api/Controllers/CollectionController.cs
  98. 19 29
      Jellyfin.Api/Controllers/DevicesController.cs
  99. 4 4
      Jellyfin.Api/Controllers/ImageController.cs
  100. 5 5
      Jellyfin.Api/Controllers/ItemUpdateController.cs

+ 3 - 0
.gitignore

@@ -278,3 +278,6 @@ web/
 web-src.*
 web-src.*
 MediaBrowser.WebDashboard/jellyfin-web
 MediaBrowser.WebDashboard/jellyfin-web
 apiclient/generated
 apiclient/generated
+
+# Omnisharp crash logs
+mono_crash.*.json

+ 1 - 1
Emby.Dlna/ContentDirectory/ServerItem.cs

@@ -17,7 +17,7 @@ namespace Emby.Dlna.ContentDirectory
         {
         {
             Item = item;
             Item = item;
 
 
-            if (item is IItemByName && !(item is Folder))
+            if (item is IItemByName && item is not Folder)
             {
             {
                 StubType = Dlna.ContentDirectory.StubType.Folder;
                 StubType = Dlna.ContentDirectory.StubType.Folder;
             }
             }

+ 1 - 1
Emby.Dlna/Didl/DidlBuilder.cs

@@ -748,7 +748,7 @@ namespace Emby.Dlna.Didl
                 AddValue(writer, "upnp", "publisher", studio, NsUpnp);
                 AddValue(writer, "upnp", "publisher", studio, NsUpnp);
             }
             }
 
 
-            if (!(item is Folder))
+            if (item is not Folder)
             {
             {
                 if (filter.Contains("dc:description"))
                 if (filter.Contains("dc:description"))
                 {
                 {

+ 3 - 1
Emby.Dlna/PlayTo/PlayToManager.cs

@@ -173,7 +173,9 @@ namespace Emby.Dlna.PlayTo
                 uuid = uri.ToString().GetMD5().ToString("N", CultureInfo.InvariantCulture);
                 uuid = uri.ToString().GetMD5().ToString("N", CultureInfo.InvariantCulture);
             }
             }
 
 
-            var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null);
+            var sessionInfo = await _sessionManager
+                .LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null)
+                .ConfigureAwait(false);
 
 
             var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
             var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
 
 

+ 108 - 151
Emby.Server.Implementations/ApplicationHost.cs

@@ -38,7 +38,6 @@ using Emby.Server.Implementations.Playlists;
 using Emby.Server.Implementations.Plugins;
 using Emby.Server.Implementations.Plugins;
 using Emby.Server.Implementations.QuickConnect;
 using Emby.Server.Implementations.QuickConnect;
 using Emby.Server.Implementations.ScheduledTasks;
 using Emby.Server.Implementations.ScheduledTasks;
-using Emby.Server.Implementations.Security;
 using Emby.Server.Implementations.Serialization;
 using Emby.Server.Implementations.Serialization;
 using Emby.Server.Implementations.Session;
 using Emby.Server.Implementations.Session;
 using Emby.Server.Implementations.SyncPlay;
 using Emby.Server.Implementations.SyncPlay;
@@ -59,7 +58,6 @@ using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
@@ -75,7 +73,6 @@ using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.QuickConnect;
 using MediaBrowser.Controller.QuickConnect;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Subtitles;
 using MediaBrowser.Controller.Subtitles;
@@ -117,6 +114,11 @@ namespace Emby.Server.Implementations
         /// </summary>
         /// </summary>
         private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
         private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
 
 
+        /// <summary>
+        /// The disposable parts.
+        /// </summary>
+        private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
+
         private readonly IFileSystem _fileSystemManager;
         private readonly IFileSystem _fileSystemManager;
         private readonly IConfiguration _startupConfig;
         private readonly IConfiguration _startupConfig;
         private readonly IXmlSerializer _xmlSerializer;
         private readonly IXmlSerializer _xmlSerializer;
@@ -128,6 +130,62 @@ namespace Emby.Server.Implementations
         private ISessionManager _sessionManager;
         private ISessionManager _sessionManager;
         private string[] _urlPrefixes;
         private string[] _urlPrefixes;
 
 
+        /// <summary>
+        /// Gets or sets all concrete types.
+        /// </summary>
+        /// <value>All concrete types.</value>
+        private Type[] _allConcreteTypes;
+
+        private DeviceId _deviceId;
+
+        private bool _disposed = false;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
+        /// </summary>
+        /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
+        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
+        /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
+        /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
+        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+        /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
+        public ApplicationHost(
+            IServerApplicationPaths applicationPaths,
+            ILoggerFactory loggerFactory,
+            IStartupOptions options,
+            IConfiguration startupConfig,
+            IFileSystem fileSystem,
+            IServiceCollection serviceCollection)
+        {
+            ApplicationPaths = applicationPaths;
+            LoggerFactory = loggerFactory;
+            _startupOptions = options;
+            _startupConfig = startupConfig;
+            _fileSystemManager = fileSystem;
+            ServiceCollection = serviceCollection;
+
+            Logger = LoggerFactory.CreateLogger<ApplicationHost>();
+            fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+
+            ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
+            ApplicationVersionString = ApplicationVersion.ToString(3);
+            ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
+
+            _xmlSerializer = new MyXmlSerializer();
+            ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
+            _pluginManager = new PluginManager(
+                LoggerFactory.CreateLogger<PluginManager>(),
+                this,
+                ConfigurationManager.Configuration,
+                ApplicationPaths.PluginsPath,
+                ApplicationVersion);
+        }
+
+        /// <summary>
+        /// Occurs when [has pending restart changed].
+        /// </summary>
+        public event EventHandler HasPendingRestartChanged;
+
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether this instance can self restart.
         /// Gets a value indicating whether this instance can self restart.
         /// </summary>
         /// </summary>
@@ -158,11 +216,6 @@ namespace Emby.Server.Implementations
         /// </summary>
         /// </summary>
         public INetworkManager NetManager { get; internal set; }
         public INetworkManager NetManager { get; internal set; }
 
 
-        /// <summary>
-        /// Occurs when [has pending restart changed].
-        /// </summary>
-        public event EventHandler HasPendingRestartChanged;
-
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether this instance has changes that require the entire application to restart.
         /// Gets a value indicating whether this instance has changes that require the entire application to restart.
         /// </summary>
         /// </summary>
@@ -190,17 +243,6 @@ namespace Emby.Server.Implementations
         /// <value>The application paths.</value>
         /// <value>The application paths.</value>
         protected IServerApplicationPaths ApplicationPaths { get; set; }
         protected IServerApplicationPaths ApplicationPaths { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets all concrete types.
-        /// </summary>
-        /// <value>All concrete types.</value>
-        private Type[] _allConcreteTypes;
-
-        /// <summary>
-        /// The disposable parts.
-        /// </summary>
-        private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
-
         /// <summary>
         /// <summary>
         /// Gets or sets the configuration manager.
         /// Gets or sets the configuration manager.
         /// </summary>
         /// </summary>
@@ -227,47 +269,55 @@ namespace Emby.Server.Implementations
         /// </summary>
         /// </summary>
         public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
         public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
 
 
+        /// <inheritdoc />
+        public Version ApplicationVersion { get; }
+
+        /// <inheritdoc />
+        public string ApplicationVersionString { get; }
+
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
+        /// Gets the current application user agent.
         /// </summary>
         /// </summary>
-        /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
-        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
-        /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
-        /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
-        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
-        /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
-        public ApplicationHost(
-            IServerApplicationPaths applicationPaths,
-            ILoggerFactory loggerFactory,
-            IStartupOptions options,
-            IConfiguration startupConfig,
-            IFileSystem fileSystem,
-            IServiceCollection serviceCollection)
-        {
-            ApplicationPaths = applicationPaths;
-            LoggerFactory = loggerFactory;
-            _startupOptions = options;
-            _startupConfig = startupConfig;
-            _fileSystemManager = fileSystem;
-            ServiceCollection = serviceCollection;
+        /// <value>The application user agent.</value>
+        public string ApplicationUserAgent { get; }
 
 
-            Logger = LoggerFactory.CreateLogger<ApplicationHost>();
-            fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+        /// <summary>
+        /// Gets the email address for use within a comment section of a user agent field.
+        /// Presently used to provide contact information to MusicBrainz service.
+        /// </summary>
+        public string ApplicationUserAgentAddress => "team@jellyfin.org";
 
 
-            ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
-            ApplicationVersionString = ApplicationVersion.ToString(3);
-            ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
+        /// <summary>
+        /// Gets the current application name.
+        /// </summary>
+        /// <value>The application name.</value>
+        public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
 
 
-            _xmlSerializer = new MyXmlSerializer();
-            ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
-            _pluginManager = new PluginManager(
-                LoggerFactory.CreateLogger<PluginManager>(),
-                this,
-                ConfigurationManager.Configuration,
-                ApplicationPaths.PluginsPath,
-                ApplicationVersion);
+        public string SystemId
+        {
+            get
+            {
+                _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
+
+                return _deviceId.Value;
+            }
         }
         }
 
 
+        /// <inheritdoc/>
+        public string Name => ApplicationProductName;
+
+        private CertificateInfo CertificateInfo { get; set; }
+
+        public X509Certificate2 Certificate { get; private set; }
+
+        /// <inheritdoc/>
+        public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps;
+
+        public string FriendlyName =>
+            string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName)
+                ? Environment.MachineName
+                : ConfigurationManager.Configuration.ServerName;
+
         /// <summary>
         /// <summary>
         /// Temporary function to migration network settings out of system.xml and into network.xml.
         /// Temporary function to migration network settings out of system.xml and into network.xml.
         /// TODO: remove at the point when a fixed migration path has been decided upon.
         /// TODO: remove at the point when a fixed migration path has been decided upon.
@@ -300,45 +350,6 @@ namespace Emby.Server.Implementations
                 .Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase);
                 .Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase);
         }
         }
 
 
-        /// <inheritdoc />
-        public Version ApplicationVersion { get; }
-
-        /// <inheritdoc />
-        public string ApplicationVersionString { get; }
-
-        /// <summary>
-        /// Gets the current application user agent.
-        /// </summary>
-        /// <value>The application user agent.</value>
-        public string ApplicationUserAgent { get; }
-
-        /// <summary>
-        /// Gets the email address for use within a comment section of a user agent field.
-        /// Presently used to provide contact information to MusicBrainz service.
-        /// </summary>
-        public string ApplicationUserAgentAddress => "team@jellyfin.org";
-
-        /// <summary>
-        /// Gets the current application name.
-        /// </summary>
-        /// <value>The application name.</value>
-        public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
-
-        private DeviceId _deviceId;
-
-        public string SystemId
-        {
-            get
-            {
-                _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
-
-                return _deviceId.Value;
-            }
-        }
-
-        /// <inheritdoc/>
-        public string Name => ApplicationProductName;
-
         /// <summary>
         /// <summary>
         /// Creates an instance of type and resolves all constructor dependencies.
         /// Creates an instance of type and resolves all constructor dependencies.
         /// </summary>
         /// </summary>
@@ -456,6 +467,7 @@ namespace Emby.Server.Implementations
         /// <summary>
         /// <summary>
         /// Runs the startup tasks.
         /// Runs the startup tasks.
         /// </summary>
         /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns><see cref="Task" />.</returns>
         /// <returns><see cref="Task" />.</returns>
         public async Task RunStartupTasksAsync(CancellationToken cancellationToken)
         public async Task RunStartupTasksAsync(CancellationToken cancellationToken)
         {
         {
@@ -469,7 +481,7 @@ namespace Emby.Server.Implementations
 
 
             _mediaEncoder.SetFFmpegPath();
             _mediaEncoder.SetFFmpegPath();
 
 
-            Logger.LogInformation("ServerId: {0}", SystemId);
+            Logger.LogInformation("ServerId: {ServerId}", SystemId);
 
 
             var entryPoints = GetExports<IServerEntryPoint>();
             var entryPoints = GetExports<IServerEntryPoint>();
 
 
@@ -594,8 +606,6 @@ namespace Emby.Server.Implementations
 
 
             ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
             ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
 
 
-            ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
-
             ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
             ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
             ServiceCollection.AddSingleton<EncodingHelper>();
             ServiceCollection.AddSingleton<EncodingHelper>();
 
 
@@ -617,8 +627,6 @@ namespace Emby.Server.Implementations
 
 
             ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
             ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
 
 
-            ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>();
-
             ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
             ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
 
 
             ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
             ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
@@ -654,8 +662,7 @@ namespace Emby.Server.Implementations
 
 
             ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
             ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
 
 
-            ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
-            ServiceCollection.AddSingleton<ISessionContext, SessionContext>();
+            ServiceCollection.AddScoped<ISessionContext, SessionContext>();
 
 
             ServiceCollection.AddSingleton<IAuthService, AuthService>();
             ServiceCollection.AddSingleton<IAuthService, AuthService>();
             ServiceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
             ServiceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
@@ -684,8 +691,6 @@ namespace Emby.Server.Implementations
             _mediaEncoder = Resolve<IMediaEncoder>();
             _mediaEncoder = Resolve<IMediaEncoder>();
             _sessionManager = Resolve<ISessionManager>();
             _sessionManager = Resolve<ISessionManager>();
 
 
-            ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
-
             SetStaticProperties();
             SetStaticProperties();
 
 
             var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
             var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
@@ -866,10 +871,6 @@ namespace Emby.Server.Implementations
             }
             }
         }
         }
 
 
-        private CertificateInfo CertificateInfo { get; set; }
-
-        public X509Certificate2 Certificate { get; private set; }
-
         private IEnumerable<string> GetUrlPrefixes()
         private IEnumerable<string> GetUrlPrefixes()
         {
         {
             var hosts = new[] { "+" };
             var hosts = new[] { "+" };
@@ -1123,9 +1124,6 @@ namespace Emby.Server.Implementations
             };
             };
         }
         }
 
 
-        /// <inheritdoc/>
-        public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps;
-
         /// <inheritdoc/>
         /// <inheritdoc/>
         public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null)
         public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null)
         {
         {
@@ -1212,14 +1210,7 @@ namespace Emby.Server.Implementations
             }.ToString().TrimEnd('/');
             }.ToString().TrimEnd('/');
         }
         }
 
 
-        public string FriendlyName =>
-            string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName)
-                ? Environment.MachineName
-                : ConfigurationManager.Configuration.ServerName;
-
-        /// <summary>
-        /// Shuts down.
-        /// </summary>
+        /// <inheritdoc />
         public async Task Shutdown()
         public async Task Shutdown()
         {
         {
             if (IsShuttingDown)
             if (IsShuttingDown)
@@ -1257,41 +1248,7 @@ namespace Emby.Server.Implementations
             }
             }
         }
         }
 
 
-        public virtual void LaunchUrl(string url)
-        {
-            if (!CanLaunchWebBrowser)
-            {
-                throw new NotSupportedException();
-            }
-
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    FileName = url,
-                    UseShellExecute = true,
-                    ErrorDialog = false
-                },
-                EnableRaisingEvents = true
-            };
-            process.Exited += (sender, args) => ((Process)sender).Dispose();
-
-            try
-            {
-                process.Start();
-            }
-            catch (Exception ex)
-            {
-                Logger.LogError(ex, "Error launching url: {url}", url);
-                throw;
-            }
-        }
-
-        private bool _disposed = false;
-
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
+        /// <inheritdoc />
         public void Dispose()
         public void Dispose()
         {
         {
             Dispose(true);
             Dispose(true);

+ 3 - 3
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Channels
             var internalChannel = _libraryManager.GetItemById(item.ChannelId);
             var internalChannel = _libraryManager.GetItemById(item.ChannelId);
             var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
             var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
 
 
-            return !(channel is IDisableMediaSourceDisplay);
+            return channel is not IDisableMediaSourceDisplay;
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -1079,11 +1079,11 @@ namespace Emby.Server.Implementations.Channels
 
 
             // was used for status
             // was used for status
             // if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
             // if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
-            //{
+            // {
             //    item.ExternalEtag = info.Etag;
             //    item.ExternalEtag = info.Etag;
             //    forceUpdate = true;
             //    forceUpdate = true;
             //    _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
             //    _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
-            //}
+            // }
 
 
             if (!internalChannelId.Equals(item.ChannelId))
             if (!internalChannelId.Equals(item.ChannelId))
             {
             {

+ 3 - 3
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Collections
 
 
             var libraryOptions = new LibraryOptions
             var libraryOptions = new LibraryOptions
             {
             {
-                PathInfos = new[] { new MediaPathInfo { Path = path } },
+                PathInfos = new[] { new MediaPathInfo(path) },
                 EnableRealtimeMonitor = false,
                 EnableRealtimeMonitor = false,
                 SaveLocalMetadata = true
                 SaveLocalMetadata = true
             };
             };
@@ -196,8 +196,8 @@ namespace Emby.Server.Implementations.Collections
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids)
-            => AddToCollectionAsync(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
+        public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
+            => AddToCollectionAsync(collectionId, itemIds, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
 
 
         private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         {
         {

+ 1 - 1
Emby.Server.Implementations/Data/BaseSqliteRepository.cs

@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Data
         protected virtual int? CacheSize => null;
         protected virtual int? CacheSize => null;
 
 
         /// <summary>
         /// <summary>
-        /// Gets the journal mode. <see href="https://www.sqlite.org/pragma.html#pragma_journal_mode" />
+        /// Gets the journal mode. <see href="https://www.sqlite.org/pragma.html#pragma_journal_mode" />.
         /// </summary>
         /// </summary>
         /// <value>The journal mode.</value>
         /// <value>The journal mode.</value>
         protected virtual string JournalMode => "TRUNCATE";
         protected virtual string JournalMode => "TRUNCATE";

+ 21 - 16
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -75,6 +75,12 @@ namespace Emby.Server.Implementations.Data
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
         /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
         /// </summary>
         /// </summary>
+        /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+        /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
+        /// <param name="logger">Instance of the <see cref="ILogger{SqliteItemRepository}"/> interface.</param>
+        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+        /// <param name="imageProcessor">Instance of the <see cref="IImageProcessor"/> interface.</param>
+        /// <exception cref="ArgumentNullException">config is null.</exception>
         public SqliteItemRepository(
         public SqliteItemRepository(
             IServerConfigurationManager config,
             IServerConfigurationManager config,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
@@ -1135,15 +1141,25 @@ namespace Emby.Server.Implementations.Data
                 Path = RestorePath(path.ToString())
                 Path = RestorePath(path.ToString())
             };
             };
 
 
-            if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
+            if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)
+                && ticks >= DateTime.MinValue.Ticks
+                && ticks <= DateTime.MaxValue.Ticks)
             {
             {
                 image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
                 image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
             }
             }
+            else
+            {
+                return null;
+            }
 
 
             if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
             if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
             {
             {
                 image.Type = type;
                 image.Type = type;
             }
             }
+            else
+            {
+                return null;
+            }
 
 
             // Optional parameters: width*height*blurhash
             // Optional parameters: width*height*blurhash
             if (nextSegment + 1 < value.Length - 1)
             if (nextSegment + 1 < value.Length - 1)
@@ -1886,12 +1902,7 @@ namespace Emby.Server.Implementations.Data
             return result;
             return result;
         }
         }
 
 
-        /// <summary>
-        /// Gets chapters for an item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>IEnumerable{ChapterInfo}.</returns>
-        /// <exception cref="ArgumentNullException">id</exception>
+        /// <inheritdoc />
         public List<ChapterInfo> GetChapters(BaseItem item)
         public List<ChapterInfo> GetChapters(BaseItem item)
         {
         {
             CheckDisposed();
             CheckDisposed();
@@ -1914,13 +1925,7 @@ namespace Emby.Server.Implementations.Data
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Gets a single chapter for an item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="index">The index.</param>
-        /// <returns>ChapterInfo.</returns>
-        /// <exception cref="ArgumentNullException">id</exception>
+        /// <inheritdoc />
         public ChapterInfo GetChapter(BaseItem item, int index)
         public ChapterInfo GetChapter(BaseItem item, int index)
         {
         {
             CheckDisposed();
             CheckDisposed();
@@ -2032,7 +2037,7 @@ namespace Emby.Server.Implementations.Data
 
 
                 for (var i = startIndex; i < endIndex; i++)
                 for (var i = startIndex; i < endIndex; i++)
                 {
                 {
-                    insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
+                    insertText.AppendFormat(CultureInfo.InvariantCulture, "(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
                 }
                 }
 
 
                 insertText.Length -= 1; // Remove last ,
                 insertText.Length -= 1; // Remove last ,
@@ -4879,7 +4884,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
 
 
             foreach (var t in _knownTypes)
             foreach (var t in _knownTypes)
             {
             {
-                dict[t.Name] = t.FullName ;
+                dict[t.Name] = t.FullName;
             }
             }
 
 
             dict["Program"] = typeof(LiveTvProgram).FullName;
             dict["Program"] = typeof(LiveTvProgram).FullName;

+ 26 - 27
Emby.Server.Implementations/Data/SqliteUserDataRepository.cs

@@ -129,19 +129,17 @@ namespace Emby.Server.Implementations.Data
             return list;
             return list;
         }
         }
 
 
-        /// <summary>
-        /// Saves the user data.
-        /// </summary>
-        public void SaveUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken)
+        /// <inheritdoc />
+        public void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken)
         {
         {
             if (userData == null)
             if (userData == null)
             {
             {
                 throw new ArgumentNullException(nameof(userData));
                 throw new ArgumentNullException(nameof(userData));
             }
             }
 
 
-            if (internalUserId <= 0)
+            if (userId <= 0)
             {
             {
-                throw new ArgumentNullException(nameof(internalUserId));
+                throw new ArgumentNullException(nameof(userId));
             }
             }
 
 
             if (string.IsNullOrEmpty(key))
             if (string.IsNullOrEmpty(key))
@@ -149,22 +147,23 @@ namespace Emby.Server.Implementations.Data
                 throw new ArgumentNullException(nameof(key));
                 throw new ArgumentNullException(nameof(key));
             }
             }
 
 
-            PersistUserData(internalUserId, key, userData, cancellationToken);
+            PersistUserData(userId, key, userData, cancellationToken);
         }
         }
 
 
-        public void SaveAllUserData(long internalUserId, UserItemData[] userData, CancellationToken cancellationToken)
+        /// <inheritdoc />
+        public void SaveAllUserData(long userId, UserItemData[] userData, CancellationToken cancellationToken)
         {
         {
             if (userData == null)
             if (userData == null)
             {
             {
                 throw new ArgumentNullException(nameof(userData));
                 throw new ArgumentNullException(nameof(userData));
             }
             }
 
 
-            if (internalUserId <= 0)
+            if (userId <= 0)
             {
             {
-                throw new ArgumentNullException(nameof(internalUserId));
+                throw new ArgumentNullException(nameof(userId));
             }
             }
 
 
-            PersistAllUserData(internalUserId, userData, cancellationToken);
+            PersistAllUserData(userId, userData, cancellationToken);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -174,7 +173,6 @@ namespace Emby.Server.Implementations.Data
         /// <param name="key">The key.</param>
         /// <param name="key">The key.</param>
         /// <param name="userData">The user data.</param>
         /// <param name="userData">The user data.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
         public void PersistUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken)
         public void PersistUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken)
         {
         {
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
@@ -264,19 +262,19 @@ namespace Emby.Server.Implementations.Data
         /// <summary>
         /// <summary>
         /// Gets the user data.
         /// Gets the user data.
         /// </summary>
         /// </summary>
-        /// <param name="internalUserId">The user id.</param>
+        /// <param name="userId">The user id.</param>
         /// <param name="key">The key.</param>
         /// <param name="key">The key.</param>
         /// <returns>Task{UserItemData}.</returns>
         /// <returns>Task{UserItemData}.</returns>
         /// <exception cref="ArgumentNullException">
         /// <exception cref="ArgumentNullException">
         /// userId
         /// userId
         /// or
         /// or
-        /// key
+        /// key.
         /// </exception>
         /// </exception>
-        public UserItemData GetUserData(long internalUserId, string key)
+        public UserItemData GetUserData(long userId, string key)
         {
         {
-            if (internalUserId <= 0)
+            if (userId <= 0)
             {
             {
-                throw new ArgumentNullException(nameof(internalUserId));
+                throw new ArgumentNullException(nameof(userId));
             }
             }
 
 
             if (string.IsNullOrEmpty(key))
             if (string.IsNullOrEmpty(key))
@@ -288,7 +286,7 @@ namespace Emby.Server.Implementations.Data
             {
             {
                 using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
                 using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
                 {
                 {
-                    statement.TryBind("@UserId", internalUserId);
+                    statement.TryBind("@UserId", userId);
                     statement.TryBind("@Key", key);
                     statement.TryBind("@Key", key);
 
 
                     foreach (var row in statement.ExecuteQuery())
                     foreach (var row in statement.ExecuteQuery())
@@ -301,7 +299,7 @@ namespace Emby.Server.Implementations.Data
             }
             }
         }
         }
 
 
-        public UserItemData GetUserData(long internalUserId, List<string> keys)
+        public UserItemData GetUserData(long userId, List<string> keys)
         {
         {
             if (keys == null)
             if (keys == null)
             {
             {
@@ -313,19 +311,19 @@ namespace Emby.Server.Implementations.Data
                 return null;
                 return null;
             }
             }
 
 
-            return GetUserData(internalUserId, keys[0]);
+            return GetUserData(userId, keys[0]);
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Return all user-data associated with the given user.
         /// Return all user-data associated with the given user.
         /// </summary>
         /// </summary>
-        /// <param name="internalUserId"></param>
-        /// <returns></returns>
-        public List<UserItemData> GetAllUserData(long internalUserId)
+        /// <param name="userId">The internal user id.</param>
+        /// <returns>The list of user item data.</returns>
+        public List<UserItemData> GetAllUserData(long userId)
         {
         {
-            if (internalUserId <= 0)
+            if (userId <= 0)
             {
             {
-                throw new ArgumentNullException(nameof(internalUserId));
+                throw new ArgumentNullException(nameof(userId));
             }
             }
 
 
             var list = new List<UserItemData>();
             var list = new List<UserItemData>();
@@ -334,7 +332,7 @@ namespace Emby.Server.Implementations.Data
             {
             {
                 using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId"))
                 using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId"))
                 {
                 {
-                    statement.TryBind("@UserId", internalUserId);
+                    statement.TryBind("@UserId", userId);
 
 
                     foreach (var row in statement.ExecuteQuery())
                     foreach (var row in statement.ExecuteQuery())
                     {
                     {
@@ -349,7 +347,8 @@ namespace Emby.Server.Implementations.Data
         /// <summary>
         /// <summary>
         /// Read a row from the specified reader into the provided userData object.
         /// Read a row from the specified reader into the provided userData object.
         /// </summary>
         /// </summary>
-        /// <param name="reader"></param>
+        /// <param name="reader">The list of result set values.</param>
+        /// <returns>The user item data.</returns>
         private UserItemData ReadRow(IReadOnlyList<ResultSetValue> reader)
         private UserItemData ReadRow(IReadOnlyList<ResultSetValue> reader)
         {
         {
             var userData = new UserItemData();
             var userData = new UserItemData();

+ 0 - 146
Emby.Server.Implementations/Devices/DeviceManager.cs

@@ -1,146 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using Jellyfin.Data.Events;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Devices;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Session;
-
-namespace Emby.Server.Implementations.Devices
-{
-    public class DeviceManager : IDeviceManager
-    {
-        private readonly IUserManager _userManager;
-        private readonly IAuthenticationRepository _authRepo;
-        private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new ();
-
-        public DeviceManager(IAuthenticationRepository authRepo, IUserManager userManager)
-        {
-            _userManager = userManager;
-            _authRepo = authRepo;
-        }
-
-        public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
-
-        public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
-        {
-            _capabilitiesMap[deviceId] = capabilities;
-        }
-
-        public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
-        {
-            _authRepo.UpdateDeviceOptions(deviceId, options);
-
-            DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options)));
-        }
-
-        public DeviceOptions GetDeviceOptions(string deviceId)
-        {
-            return _authRepo.GetDeviceOptions(deviceId);
-        }
-
-        public ClientCapabilities GetCapabilities(string id)
-        {
-            return _capabilitiesMap.TryGetValue(id, out ClientCapabilities result)
-                ? result
-                : new ClientCapabilities();
-        }
-
-        public DeviceInfo GetDevice(string id)
-        {
-            var session = _authRepo.Get(new AuthenticationInfoQuery
-            {
-                DeviceId = id
-            }).Items.FirstOrDefault();
-
-            var device = session == null ? null : ToDeviceInfo(session);
-
-            return device;
-        }
-
-        public QueryResult<DeviceInfo> GetDevices(DeviceQuery query)
-        {
-            IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
-            {
-                // UserId = query.UserId
-                HasUser = true
-            }).Items;
-
-            // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
-            if (query.SupportsSync.HasValue)
-            {
-                var val = query.SupportsSync.Value;
-
-                sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val);
-            }
-
-            if (!query.UserId.Equals(Guid.Empty))
-            {
-                var user = _userManager.GetUserById(query.UserId);
-
-                sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
-            }
-
-            var array = sessions.Select(ToDeviceInfo).ToArray();
-
-            return new QueryResult<DeviceInfo>(array);
-        }
-
-        private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo)
-        {
-            var caps = GetCapabilities(authInfo.DeviceId);
-
-            return new DeviceInfo
-            {
-                AppName = authInfo.AppName,
-                AppVersion = authInfo.AppVersion,
-                Id = authInfo.DeviceId,
-                LastUserId = authInfo.UserId,
-                LastUserName = authInfo.UserName,
-                Name = authInfo.DeviceName,
-                DateLastActivity = authInfo.DateLastActivity,
-                IconUrl = caps?.IconUrl
-            };
-        }
-
-        public bool CanAccessDevice(User user, string deviceId)
-        {
-            if (user == null)
-            {
-                throw new ArgumentException("user not found");
-            }
-
-            if (string.IsNullOrEmpty(deviceId))
-            {
-                throw new ArgumentNullException(nameof(deviceId));
-            }
-
-            if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator))
-            {
-                return true;
-            }
-
-            if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase))
-            {
-                var capabilities = GetCapabilities(deviceId);
-
-                if (capabilities != null && capabilities.SupportsPersistentIdentifier)
-                {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-    }
-}

+ 25 - 28
Emby.Server.Implementations/Dto/DtoService.cs

@@ -51,8 +51,6 @@ namespace Emby.Server.Implementations.Dto
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
         private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
 
 
-        private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
-
         public DtoService(
         public DtoService(
             ILogger<DtoService> logger,
             ILogger<DtoService> logger,
             ILibraryManager libraryManager,
             ILibraryManager libraryManager,
@@ -75,6 +73,8 @@ namespace Emby.Server.Implementations.Dto
             _livetvManagerFactory = livetvManagerFactory;
             _livetvManagerFactory = livetvManagerFactory;
         }
         }
 
 
+        private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
+
         /// <inheritdoc />
         /// <inheritdoc />
         public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
         public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
         {
         {
@@ -507,7 +507,6 @@ namespace Emby.Server.Implementations.Dto
         /// </summary>
         /// </summary>
         /// <param name="dto">The dto.</param>
         /// <param name="dto">The dto.</param>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
-        /// <returns>Task.</returns>
         private void AttachPeople(BaseItemDto dto, BaseItem item)
         private void AttachPeople(BaseItemDto dto, BaseItem item)
         {
         {
             // Ordering by person type to ensure actors and artists are at the front.
             // Ordering by person type to ensure actors and artists are at the front.
@@ -616,7 +615,6 @@ namespace Emby.Server.Implementations.Dto
         /// </summary>
         /// </summary>
         /// <param name="dto">The dto.</param>
         /// <param name="dto">The dto.</param>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
-        /// <returns>Task.</returns>
         private void AttachStudios(BaseItemDto dto, BaseItem item)
         private void AttachStudios(BaseItemDto dto, BaseItem item)
         {
         {
             dto.Studios = item.Studios
             dto.Studios = item.Studios
@@ -807,7 +805,7 @@ namespace Emby.Server.Implementations.Dto
 
 
             dto.MediaType = item.MediaType;
             dto.MediaType = item.MediaType;
 
 
-            if (!(item is LiveTvProgram))
+            if (item is not LiveTvProgram)
             {
             {
                 dto.LocationType = item.LocationType;
                 dto.LocationType = item.LocationType;
             }
             }
@@ -928,9 +926,9 @@ namespace Emby.Server.Implementations.Dto
                 }
                 }
 
 
                 // if (options.ContainsField(ItemFields.MediaSourceCount))
                 // if (options.ContainsField(ItemFields.MediaSourceCount))
-                //{
+                // {
                 // Songs always have one
                 // Songs always have one
-                //}
+                // }
             }
             }
 
 
             if (item is IHasArtist hasArtist)
             if (item is IHasArtist hasArtist)
@@ -938,10 +936,10 @@ namespace Emby.Server.Implementations.Dto
                 dto.Artists = hasArtist.Artists;
                 dto.Artists = hasArtist.Artists;
 
 
                 // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
                 // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
-                //{
+                // {
                 //    EnableTotalRecordCount = false,
                 //    EnableTotalRecordCount = false,
                 //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
                 //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
-                //});
+                // });
 
 
                 // dto.ArtistItems = artistItems.Items
                 // dto.ArtistItems = artistItems.Items
                 //    .Select(i =>
                 //    .Select(i =>
@@ -958,7 +956,7 @@ namespace Emby.Server.Implementations.Dto
                 // Include artists that are not in the database yet, e.g., just added via metadata editor
                 // Include artists that are not in the database yet, e.g., just added via metadata editor
                 // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
                 // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
                 dto.ArtistItems = hasArtist.Artists
                 dto.ArtistItems = hasArtist.Artists
-                    //.Except(foundArtists, new DistinctNameComparer())
+                    // .Except(foundArtists, new DistinctNameComparer())
                     .Select(i =>
                     .Select(i =>
                     {
                     {
                         // This should not be necessary but we're seeing some cases of it
                         // This should not be necessary but we're seeing some cases of it
@@ -990,10 +988,10 @@ namespace Emby.Server.Implementations.Dto
                 dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
                 dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 
 
                 // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
                 // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
-                //{
+                // {
                 //    EnableTotalRecordCount = false,
                 //    EnableTotalRecordCount = false,
                 //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
                 //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
-                //});
+                // });
 
 
                 // dto.AlbumArtists = artistItems.Items
                 // dto.AlbumArtists = artistItems.Items
                 //    .Select(i =>
                 //    .Select(i =>
@@ -1008,7 +1006,7 @@ namespace Emby.Server.Implementations.Dto
                 //    .ToList();
                 //    .ToList();
 
 
                 dto.AlbumArtists = hasAlbumArtist.AlbumArtists
                 dto.AlbumArtists = hasAlbumArtist.AlbumArtists
-                    //.Except(foundArtists, new DistinctNameComparer())
+                    // .Except(foundArtists, new DistinctNameComparer())
                     .Select(i =>
                     .Select(i =>
                     {
                     {
                         // This should not be necessary but we're seeing some cases of it
                         // This should not be necessary but we're seeing some cases of it
@@ -1035,8 +1033,7 @@ namespace Emby.Server.Implementations.Dto
             }
             }
 
 
             // Add video info
             // Add video info
-            var video = item as Video;
-            if (video != null)
+            if (item is Video video)
             {
             {
                 dto.VideoType = video.VideoType;
                 dto.VideoType = video.VideoType;
                 dto.Video3DFormat = video.Video3DFormat;
                 dto.Video3DFormat = video.Video3DFormat;
@@ -1075,9 +1072,7 @@ namespace Emby.Server.Implementations.Dto
             if (options.ContainsField(ItemFields.MediaStreams))
             if (options.ContainsField(ItemFields.MediaStreams))
             {
             {
                 // Add VideoInfo
                 // Add VideoInfo
-                var iHasMediaSources = item as IHasMediaSources;
-
-                if (iHasMediaSources != null)
+                if (item is IHasMediaSources)
                 {
                 {
                     MediaStream[] mediaStreams;
                     MediaStream[] mediaStreams;
 
 
@@ -1146,7 +1141,7 @@ namespace Emby.Server.Implementations.Dto
                 // TODO maybe remove the if statement entirely
                 // TODO maybe remove the if statement entirely
                 // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
                 // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
                 {
                 {
-                    episodeSeries = episodeSeries ?? episode.Series;
+                    episodeSeries ??= episode.Series;
                     if (episodeSeries != null)
                     if (episodeSeries != null)
                     {
                     {
                         dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
                         dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
@@ -1159,7 +1154,7 @@ namespace Emby.Server.Implementations.Dto
 
 
                 if (options.ContainsField(ItemFields.SeriesStudio))
                 if (options.ContainsField(ItemFields.SeriesStudio))
                 {
                 {
-                    episodeSeries = episodeSeries ?? episode.Series;
+                    episodeSeries ??= episode.Series;
                     if (episodeSeries != null)
                     if (episodeSeries != null)
                     {
                     {
                         dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
                         dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
@@ -1172,7 +1167,7 @@ namespace Emby.Server.Implementations.Dto
             {
             {
                 dto.AirDays = series.AirDays;
                 dto.AirDays = series.AirDays;
                 dto.AirTime = series.AirTime;
                 dto.AirTime = series.AirTime;
-                dto.Status = series.Status.HasValue ? series.Status.Value.ToString() : null;
+                dto.Status = series.Status?.ToString();
             }
             }
 
 
             // Add SeasonInfo
             // Add SeasonInfo
@@ -1185,7 +1180,7 @@ namespace Emby.Server.Implementations.Dto
 
 
                 if (options.ContainsField(ItemFields.SeriesStudio))
                 if (options.ContainsField(ItemFields.SeriesStudio))
                 {
                 {
-                    series = series ?? season.Series;
+                    series ??= season.Series;
                     if (series != null)
                     if (series != null)
                     {
                     {
                         dto.SeriesStudio = series.Studios.FirstOrDefault();
                         dto.SeriesStudio = series.Studios.FirstOrDefault();
@@ -1196,7 +1191,7 @@ namespace Emby.Server.Implementations.Dto
                 // TODO maybe remove the if statement entirely
                 // TODO maybe remove the if statement entirely
                 // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
                 // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
                 {
                 {
-                    series = series ?? season.Series;
+                    series ??= season.Series;
                     if (series != null)
                     if (series != null)
                     {
                     {
                         dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
                         dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
@@ -1283,7 +1278,7 @@ namespace Emby.Server.Implementations.Dto
 
 
             var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
             var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
 
 
-            if (parent == null && !(originalItem is UserRootFolder) && !(originalItem is UserView) && !(originalItem is AggregateFolder) && !(originalItem is ICollectionFolder) && !(originalItem is Channel))
+            if (parent == null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is not AggregateFolder && originalItem is not ICollectionFolder && originalItem is not Channel)
             {
             {
                 parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
                 parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
             }
             }
@@ -1316,9 +1311,12 @@ namespace Emby.Server.Implementations.Dto
 
 
             var imageTags = dto.ImageTags;
             var imageTags = dto.ImageTags;
 
 
-            while (((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0) || parent is Series) &&
-                (parent = parent ?? (isFirst ? GetImageDisplayParent(item, item) ?? owner : parent)) != null)
+            while ((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
+                || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
+                || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
+                || parent is Series)
             {
             {
+                parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent;
                 if (parent == null)
                 if (parent == null)
                 {
                 {
                     break;
                     break;
@@ -1348,7 +1346,7 @@ namespace Emby.Server.Implementations.Dto
                     }
                     }
                 }
                 }
 
 
-                if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
+                if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && parent is not ICollectionFolder && parent is not UserView)
                 {
                 {
                     var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
                     var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
 
 
@@ -1398,7 +1396,6 @@ namespace Emby.Server.Implementations.Dto
         /// </summary>
         /// </summary>
         /// <param name="dto">The dto.</param>
         /// <param name="dto">The dto.</param>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
-        /// <returns>Task.</returns>
         public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
         public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
         {
         {
             dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
             dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);

+ 2 - 2
Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.EntryPoints
 
 
         private static bool EnableRefreshMessage(BaseItem item)
         private static bool EnableRefreshMessage(BaseItem item)
         {
         {
-            if (!(item is Folder folder))
+            if (item is not Folder folder)
             {
             {
                 return false;
                 return false;
             }
             }
@@ -403,7 +403,7 @@ namespace Emby.Server.Implementations.EntryPoints
                 return false;
                 return false;
             }
             }
 
 
-            if (item is IItemByName && !(item is MusicArtist))
+            if (item is IItemByName && item is not MusicArtist)
             {
             {
                 return false;
                 return false;
             }
             }

+ 3 - 0
Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs

@@ -37,6 +37,9 @@ namespace Emby.Server.Implementations.EntryPoints
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
         /// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
         /// </summary>
         /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger{UdpServerEntryPoint}"/> interface.</param>
+        /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
+        /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
         public UdpServerEntryPoint(
         public UdpServerEntryPoint(
             ILogger<UdpServerEntryPoint> logger,
             ILogger<UdpServerEntryPoint> logger,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,

+ 3 - 2
Emby.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -1,5 +1,6 @@
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
+using System.Threading.Tasks;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
@@ -17,9 +18,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
             _authorizationContext = authorizationContext;
             _authorizationContext = authorizationContext;
         }
         }
 
 
-        public AuthorizationInfo Authenticate(HttpRequest request)
+        public async Task<AuthorizationInfo> Authenticate(HttpRequest request)
         {
         {
-            var auth = _authorizationContext.GetAuthorizationInfo(request);
+            var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false);
 
 
             if (!auth.HasToken)
             if (!auth.HasToken)
             {
             {

+ 14 - 7
Emby.Server.Implementations/HttpServer/Security/SessionContext.cs

@@ -1,6 +1,7 @@
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
 using System;
 using System;
+using System.Threading.Tasks;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -23,27 +24,33 @@ namespace Emby.Server.Implementations.HttpServer.Security
             _sessionManager = sessionManager;
             _sessionManager = sessionManager;
         }
         }
 
 
-        public SessionInfo GetSession(HttpContext requestContext)
+        public async Task<SessionInfo> GetSession(HttpContext requestContext)
         {
         {
-            var authorization = _authContext.GetAuthorizationInfo(requestContext);
+            var authorization = await _authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false);
 
 
             var user = authorization.User;
             var user = authorization.User;
-            return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user);
+            return await _sessionManager.LogSessionActivity(
+                authorization.Client,
+                authorization.Version,
+                authorization.DeviceId,
+                authorization.Device,
+                requestContext.GetNormalizedRemoteIp().ToString(),
+                user).ConfigureAwait(false);
         }
         }
 
 
-        public SessionInfo GetSession(object requestContext)
+        public Task<SessionInfo> GetSession(object requestContext)
         {
         {
             return GetSession((HttpContext)requestContext);
             return GetSession((HttpContext)requestContext);
         }
         }
 
 
-        public User? GetUser(HttpContext requestContext)
+        public async Task<User?> GetUser(HttpContext requestContext)
         {
         {
-            var session = GetSession(requestContext);
+            var session = await GetSession(requestContext).ConfigureAwait(false);
 
 
             return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
             return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
         }
         }
 
 
-        public User? GetUser(object requestContext)
+        public Task<User?> GetUser(object requestContext)
         {
         {
             return GetUser(((HttpRequest)requestContext).HttpContext);
             return GetUser(((HttpRequest)requestContext).HttpContext);
         }
         }

+ 2 - 2
Emby.Server.Implementations/HttpServer/WebSocketConnection.cs

@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.HttpServer
         public event EventHandler<EventArgs>? Closed;
         public event EventHandler<EventArgs>? Closed;
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the remote end point.
+        /// Gets the remote end point.
         /// </summary>
         /// </summary>
         public IPAddress? RemoteEndPoint { get; }
         public IPAddress? RemoteEndPoint { get; }
 
 
@@ -82,7 +82,7 @@ namespace Emby.Server.Implementations.HttpServer
         public DateTime LastKeepAliveDate { get; set; }
         public DateTime LastKeepAliveDate { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the query string.
+        /// Gets the query string.
         /// </summary>
         /// </summary>
         /// <value>The query string.</value>
         /// <value>The query string.</value>
         public IQueryCollection QueryString { get; }
         public IQueryCollection QueryString { get; }

+ 1 - 1
Emby.Server.Implementations/HttpServer/WebSocketManager.cs

@@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.HttpServer
         /// <inheritdoc />
         /// <inheritdoc />
         public async Task WebSocketRequestHandler(HttpContext context)
         public async Task WebSocketRequestHandler(HttpContext context)
         {
         {
-            _ = _authService.Authenticate(context.Request);
+            _ = await _authService.Authenticate(context.Request).ConfigureAwait(false);
             try
             try
             {
             {
                 _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
                 _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);

+ 3 - 3
Emby.Server.Implementations/IO/ManagedFileSystem.cs

@@ -423,7 +423,7 @@ namespace Emby.Server.Implementations.IO
             }
             }
         }
         }
 
 
-        public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
+        public virtual void SetAttributes(string path, bool isHidden, bool readOnly)
         {
         {
             if (!OperatingSystem.IsWindows())
             if (!OperatingSystem.IsWindows())
             {
             {
@@ -437,14 +437,14 @@ namespace Emby.Server.Implementations.IO
                 return;
                 return;
             }
             }
 
 
-            if (info.IsReadOnly == isReadOnly && info.IsHidden == isHidden)
+            if (info.IsReadOnly == readOnly && info.IsHidden == isHidden)
             {
             {
                 return;
                 return;
             }
             }
 
 
             var attributes = File.GetAttributes(path);
             var attributes = File.GetAttributes(path);
 
 
-            if (isReadOnly)
+            if (readOnly)
             {
             {
                 attributes = attributes | FileAttributes.ReadOnly;
                 attributes = attributes | FileAttributes.ReadOnly;
             }
             }

+ 1 - 1
Emby.Server.Implementations/IStartupOptions.cs

@@ -10,7 +10,7 @@ namespace Emby.Server.Implementations
         string? FFmpegPath { get; }
         string? FFmpegPath { get; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the value of the --service command line option.
+        /// Gets a value value indicating whether to run as service by the --service command line option.
         /// </summary>
         /// </summary>
         bool IsService { get; }
         bool IsService { get; }
 
 

+ 1 - 1
Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs

@@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.Images
 
 
         public int Order => 0;
         public int Order => 0;
 
 
-        protected virtual bool Supports(BaseItem _) => true;
+        protected virtual bool Supports(BaseItem item) => true;
 
 
         public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
         public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
         {
         {

+ 6 - 6
Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs

@@ -30,27 +30,27 @@ namespace Emby.Server.Implementations.Images
 
 
             string[] includeItemTypes;
             string[] includeItemTypes;
 
 
-            if (string.Equals(viewType, CollectionType.Movies))
+            if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
             {
             {
                 includeItemTypes = new string[] { "Movie" };
                 includeItemTypes = new string[] { "Movie" };
             }
             }
-            else if (string.Equals(viewType, CollectionType.TvShows))
+            else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
             {
             {
                 includeItemTypes = new string[] { "Series" };
                 includeItemTypes = new string[] { "Series" };
             }
             }
-            else if (string.Equals(viewType, CollectionType.Music))
+            else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
             {
             {
                 includeItemTypes = new string[] { "MusicAlbum" };
                 includeItemTypes = new string[] { "MusicAlbum" };
             }
             }
-            else if (string.Equals(viewType, CollectionType.Books))
+            else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
             {
             {
                 includeItemTypes = new string[] { "Book", "AudioBook" };
                 includeItemTypes = new string[] { "Book", "AudioBook" };
             }
             }
-            else if (string.Equals(viewType, CollectionType.BoxSets))
+            else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
             {
             {
                 includeItemTypes = new string[] { "BoxSet" };
                 includeItemTypes = new string[] { "BoxSet" };
             }
             }
-            else if (string.Equals(viewType, CollectionType.HomeVideos) || string.Equals(viewType, CollectionType.Photos))
+            else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
             {
             {
                 includeItemTypes = new string[] { "Video", "Photo" };
                 includeItemTypes = new string[] { "Video", "Photo" };
             }
             }

+ 15 - 20
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -287,14 +287,14 @@ namespace Emby.Server.Implementations.Library
 
 
             if (item is IItemByName)
             if (item is IItemByName)
             {
             {
-                if (!(item is MusicArtist))
+                if (item is not MusicArtist)
                 {
                 {
                     return;
                     return;
                 }
                 }
             }
             }
             else if (!item.IsFolder)
             else if (!item.IsFolder)
             {
             {
-                if (!(item is Video) && !(item is LiveTvChannel))
+                if (item is not Video && item is not LiveTvChannel)
                 {
                 {
                     return;
                     return;
                 }
                 }
@@ -866,7 +866,7 @@ namespace Emby.Server.Implementations.Library
         {
         {
             var path = Person.GetPath(name);
             var path = Person.GetPath(name);
             var id = GetItemByNameId<Person>(path);
             var id = GetItemByNameId<Person>(path);
-            if (!(GetItemById(id) is Person item))
+            if (GetItemById(id) is not Person item)
             {
             {
                 item = new Person
                 item = new Person
                 {
                 {
@@ -1761,22 +1761,20 @@ namespace Emby.Server.Implementations.Library
             return orderedItems ?? items;
             return orderedItems ?? items;
         }
         }
 
 
-        public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderByList)
+        public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
         {
         {
             var isFirst = true;
             var isFirst = true;
 
 
             IOrderedEnumerable<BaseItem> orderedItems = null;
             IOrderedEnumerable<BaseItem> orderedItems = null;
 
 
-            foreach (var orderBy in orderByList)
+            foreach (var (name, sortOrder) in orderBy)
             {
             {
-                var comparer = GetComparer(orderBy.Item1, user);
+                var comparer = GetComparer(name, user);
                 if (comparer == null)
                 if (comparer == null)
                 {
                 {
                     continue;
                     continue;
                 }
                 }
 
 
-                var sortOrder = orderBy.Item2;
-
                 if (isFirst)
                 if (isFirst)
                 {
                 {
                     orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer);
                     orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer);
@@ -2118,7 +2116,7 @@ namespace Emby.Server.Implementations.Library
 
 
         public LibraryOptions GetLibraryOptions(BaseItem item)
         public LibraryOptions GetLibraryOptions(BaseItem item)
         {
         {
-            if (!(item is CollectionFolder collectionFolder))
+            if (item is not CollectionFolder collectionFolder)
             {
             {
                 // List.Find is more performant than FirstOrDefault due to enumerator allocation
                 // List.Find is more performant than FirstOrDefault due to enumerator allocation
                 collectionFolder = GetCollectionFolders(item)
                 collectionFolder = GetCollectionFolders(item)
@@ -3076,9 +3074,9 @@ namespace Emby.Server.Implementations.Library
             });
             });
         }
         }
 
 
-        public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+        public void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
         {
         {
-            AddMediaPathInternal(virtualFolderName, pathInfo, true);
+            AddMediaPathInternal(virtualFolderName, mediaPath, true);
         }
         }
 
 
         private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions)
         private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions)
@@ -3131,11 +3129,11 @@ namespace Emby.Server.Implementations.Library
             }
             }
         }
         }
 
 
-        public void UpdateMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+        public void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
         {
         {
-            if (pathInfo == null)
+            if (mediaPath == null)
             {
             {
-                throw new ArgumentNullException(nameof(pathInfo));
+                throw new ArgumentNullException(nameof(mediaPath));
             }
             }
 
 
             var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
             var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
@@ -3148,9 +3146,9 @@ namespace Emby.Server.Implementations.Library
             var list = libraryOptions.PathInfos.ToList();
             var list = libraryOptions.PathInfos.ToList();
             foreach (var originalPathInfo in list)
             foreach (var originalPathInfo in list)
             {
             {
-                if (string.Equals(pathInfo.Path, originalPathInfo.Path, StringComparison.Ordinal))
+                if (string.Equals(mediaPath.Path, originalPathInfo.Path, StringComparison.Ordinal))
                 {
                 {
-                    originalPathInfo.NetworkPath = pathInfo.NetworkPath;
+                    originalPathInfo.NetworkPath = mediaPath.NetworkPath;
                     break;
                     break;
                 }
                 }
             }
             }
@@ -3173,10 +3171,7 @@ namespace Emby.Server.Implementations.Library
                 {
                 {
                     if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
                     if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
                     {
                     {
-                        list.Add(new MediaPathInfo
-                        {
-                            Path = location
-                        });
+                        list.Add(new MediaPathInfo(location));
                     }
                     }
                 }
                 }
 
 

+ 3 - 2
Emby.Server.Implementations/Library/MusicManager.cs

@@ -36,9 +36,10 @@ namespace Emby.Server.Implementations.Library
             return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList();
             return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList();
         }
         }
 
 
-        public List<BaseItem> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions)
+        /// <inheritdoc />
+        public List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions)
         {
         {
-            return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
+            return GetInstantMixFromGenres(artist.Genres, user, dtoOptions);
         }
         }
 
 
         public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)
         public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)

+ 6 - 6
Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs

@@ -21,11 +21,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
     /// </summary>
     /// </summary>
     public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
     public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
     {
     {
-        private readonly ILibraryManager LibraryManager;
+        private readonly ILibraryManager _libraryManager;
 
 
         public AudioResolver(ILibraryManager libraryManager)
         public AudioResolver(ILibraryManager libraryManager)
         {
         {
-            LibraryManager = libraryManager;
+            _libraryManager = libraryManager;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
                 }
                 }
 
 
                 var files = args.FileSystemChildren
                 var files = args.FileSystemChildren
-                    .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+                    .Where(i => !_libraryManager.IgnoreFile(i, args.Parent))
                     .ToList();
                     .ToList();
 
 
                 return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
                 return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
             }
             }
 
 
-            if (LibraryManager.IsAudioFile(args.Path))
+            if (_libraryManager.IsAudioFile(args.Path))
             {
             {
                 var extension = Path.GetExtension(args.Path);
                 var extension = Path.GetExtension(args.Path);
 
 
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
                 var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
                 var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
 
 
                 // For conflicting extensions, give priority to videos
                 // For conflicting extensions, give priority to videos
-                if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path))
+                if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path))
                 {
                 {
                     return null;
                     return null;
                 }
                 }
@@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
                 }
                 }
             }
             }
 
 
-            var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+            var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
 
 
             var resolver = new AudioBookListResolver(namingOptions);
             var resolver = new AudioBookListResolver(namingOptions);
             var resolverResult = resolver.Resolve(files).ToList();
             var resolverResult = resolver.Resolve(files).ToList();

+ 9 - 21
Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs

@@ -17,17 +17,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
     /// <summary>
     /// <summary>
     /// Resolves a Path into a Video or Video subclass.
     /// Resolves a Path into a Video or Video subclass.
     /// </summary>
     /// </summary>
-    /// <typeparam name="T"></typeparam>
+    /// <typeparam name="T">The type of item to resolve.</typeparam>
     public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
     public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
         where T : Video, new()
         where T : Video, new()
     {
     {
-        protected readonly ILibraryManager LibraryManager;
-
         protected BaseVideoResolver(ILibraryManager libraryManager)
         protected BaseVideoResolver(ILibraryManager libraryManager)
         {
         {
             LibraryManager = libraryManager;
             LibraryManager = libraryManager;
         }
         }
 
 
+        protected ILibraryManager LibraryManager { get; }
+
         /// <summary>
         /// <summary>
         /// Resolves the specified args.
         /// Resolves the specified args.
         /// </summary>
         /// </summary>
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
                             break;
                             break;
                         }
                         }
 
 
-                        if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService))
+                        if (IsBluRayDirectory(filename))
                         {
                         {
                             videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
                             videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
 
 
@@ -296,25 +296,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Determines whether [is blu ray directory] [the specified directory name].
+        /// Determines whether [is bluray directory] [the specified directory name].
         /// </summary>
         /// </summary>
-        protected bool IsBluRayDirectory(string fullPath, string directoryName, IDirectoryService directoryService)
+        /// <param name="directoryName">The directory name.</param>
+        /// <returns>Whether the directory is a bluray directory.</returns>
+        protected bool IsBluRayDirectory(string directoryName)
         {
         {
-            if (!string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-
-            return true;
-            // var blurayExtensions = new[]
-            //{
-            //    ".mts",
-            //    ".m2ts",
-            //    ".bdmv",
-            //    ".mpls"
-            //};
-
-            // return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
+            return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase);
         }
         }
     }
     }
 }
 }

+ 0 - 0
Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs → Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs


+ 1 - 1
Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs

@@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
     /// <summary>
     /// <summary>
     /// Class ItemResolver.
     /// Class ItemResolver.
     /// </summary>
     /// </summary>
-    /// <typeparam name="T"></typeparam>
+    /// <typeparam name="T">The type of BaseItem.</typeparam>
     public abstract class ItemResolver<T> : IItemResolver
     public abstract class ItemResolver<T> : IItemResolver
         where T : BaseItem, new()
         where T : BaseItem, new()
     {
     {

+ 2 - 2
Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -400,7 +400,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
                         return movie;
                         return movie;
                     }
                     }
 
 
-                    if (IsBluRayDirectory(child.FullName, filename, directoryService))
+                    if (IsBluRayDirectory(filename))
                     {
                     {
                         var movie = new T
                         var movie = new T
                         {
                         {
@@ -481,7 +481,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
                     return true;
                     return true;
                 }
                 }
 
 
-                if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
+                if (subfolders.Any(s => IsBluRayDirectory(s.Name)))
                 {
                 {
                     videoTypes.Add(VideoType.BluRay);
                     videoTypes.Add(VideoType.BluRay);
                     return true;
                     return true;

+ 2 - 1
Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs

@@ -18,7 +18,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
     /// </summary>
     /// </summary>
     public class PlaylistResolver : FolderResolver<Playlist>
     public class PlaylistResolver : FolderResolver<Playlist>
     {
     {
-        private string[] _musicPlaylistCollectionTypes = new string[] {
+        private string[] _musicPlaylistCollectionTypes =
+        {
             string.Empty,
             string.Empty,
             CollectionType.Music
             CollectionType.Music
         };
         };

+ 3 - 1
Emby.Server.Implementations/Library/UserDataManager.cs

@@ -177,6 +177,7 @@ namespace Emby.Server.Implementations.Library
             return dto;
             return dto;
         }
         }
 
 
+        /// <inheritdoc />
         public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options)
         public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options)
         {
         {
             var userData = GetUserData(user, item);
             var userData = GetUserData(user, item);
@@ -191,7 +192,7 @@ namespace Emby.Server.Implementations.Library
         /// </summary>
         /// </summary>
         /// <param name="data">The data.</param>
         /// <param name="data">The data.</param>
         /// <returns>DtoUserItemData.</returns>
         /// <returns>DtoUserItemData.</returns>
-        /// <exception cref="ArgumentNullException"></exception>
+        /// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
         private UserItemDataDto GetUserItemDataDto(UserItemData data)
         private UserItemDataDto GetUserItemDataDto(UserItemData data)
         {
         {
             if (data == null)
             if (data == null)
@@ -212,6 +213,7 @@ namespace Emby.Server.Implementations.Library
             };
             };
         }
         }
 
 
+        /// <inheritdoc />
         public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks)
         public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks)
         {
         {
             var playedToCompletion = false;
             var playedToCompletion = false;

+ 8 - 5
Emby.Server.Implementations/Library/Validators/StudiosValidator.cs

@@ -87,12 +87,15 @@ namespace Emby.Server.Implementations.Library.Validators
 
 
             foreach (var item in deadEntities)
             foreach (var item in deadEntities)
             {
             {
-                _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
+                _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
 
 
-                _libraryManager.DeleteItem(item, new DeleteOptions
-                {
-                    DeleteFileLocation = false
-                }, false);
+                _libraryManager.DeleteItem(
+                    item,
+                    new DeleteOptions
+                    {
+                        DeleteFileLocation = false
+                    },
+                    false);
             }
             }
 
 
             progress.Report(100);
             progress.Report(100);

+ 3 - 5
Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
 using System;
 using System;
@@ -33,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return targetFile;
             return targetFile;
         }
         }
 
 
-        public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+        public Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         {
         {
             if (directStreamProvider != null)
             if (directStreamProvider != null)
             {
             {
@@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
         private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         {
         {
-            Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
+            Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
 
 
             // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
             // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
             using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
             using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
@@ -71,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
             _logger.LogInformation("Opened recording stream from tuner provider");
             _logger.LogInformation("Opened recording stream from tuner provider");
 
 
-            Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
+            Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
 
 
             // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
             // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
             await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);
             await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);

+ 33 - 40
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -159,8 +159,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             try
             try
             {
             {
                 var recordingFolders = GetRecordingFolders().ToArray();
                 var recordingFolders = GetRecordingFolders().ToArray();
-                var virtualFolders = _libraryManager.GetVirtualFolders()
-                    .ToList();
+                var virtualFolders = _libraryManager.GetVirtualFolders();
 
 
                 var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
                 var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
 
 
@@ -177,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                         continue;
                         continue;
                     }
                     }
 
 
-                    var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray();
+                    var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo(i)).ToArray();
 
 
                     var libraryOptions = new LibraryOptions
                     var libraryOptions = new LibraryOptions
                     {
                     {
@@ -210,7 +209,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
                 foreach (var path in pathsToRemove)
                 foreach (var path in pathsToRemove)
                 {
                 {
-                    await RemovePathFromLibrary(path).ConfigureAwait(false);
+                    await RemovePathFromLibraryAsync(path).ConfigureAwait(false);
                 }
                 }
             }
             }
             catch (Exception ex)
             catch (Exception ex)
@@ -219,13 +218,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             }
             }
         }
         }
 
 
-        private async Task RemovePathFromLibrary(string path)
+        private async Task RemovePathFromLibraryAsync(string path)
         {
         {
             _logger.LogDebug("Removing path from library: {0}", path);
             _logger.LogDebug("Removing path from library: {0}", path);
 
 
             var requiresRefresh = false;
             var requiresRefresh = false;
-            var virtualFolders = _libraryManager.GetVirtualFolders()
-               .ToList();
+            var virtualFolders = _libraryManager.GetVirtualFolders();
 
 
             foreach (var virtualFolder in virtualFolders)
             foreach (var virtualFolder in virtualFolders)
             {
             {
@@ -460,7 +458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
             if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
             {
             {
                 var tunerChannelId = tunerChannel.TunerChannelId;
                 var tunerChannelId = tunerChannel.TunerChannelId;
-                if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1)
+                if (tunerChannelId.Contains(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
                     tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
                 }
                 }
@@ -612,16 +610,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
+        public Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken)
         {
         {
-            var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ?
+            var existingTimer = string.IsNullOrWhiteSpace(info.ProgramId) ?
                 null :
                 null :
-                _timerProvider.GetTimerByProgramId(timer.ProgramId);
+                _timerProvider.GetTimerByProgramId(info.ProgramId);
 
 
             if (existingTimer != null)
             if (existingTimer != null)
             {
             {
-                if (existingTimer.Status == RecordingStatus.Cancelled ||
-                    existingTimer.Status == RecordingStatus.Completed)
+                if (existingTimer.Status == RecordingStatus.Cancelled
+                    || existingTimer.Status == RecordingStatus.Completed)
                 {
                 {
                     existingTimer.Status = RecordingStatus.New;
                     existingTimer.Status = RecordingStatus.New;
                     existingTimer.IsManual = true;
                     existingTimer.IsManual = true;
@@ -634,32 +632,32 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 }
                 }
             }
             }
 
 
-            timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+            info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
 
 
             LiveTvProgram programInfo = null;
             LiveTvProgram programInfo = null;
 
 
-            if (!string.IsNullOrWhiteSpace(timer.ProgramId))
+            if (!string.IsNullOrWhiteSpace(info.ProgramId))
             {
             {
-                programInfo = GetProgramInfoFromCache(timer);
+                programInfo = GetProgramInfoFromCache(info);
             }
             }
 
 
             if (programInfo == null)
             if (programInfo == null)
             {
             {
-                _logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
-                programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
+                _logger.LogInformation("Unable to find program with Id {0}. Will search using start date", info.ProgramId);
+                programInfo = GetProgramInfoFromCache(info.ChannelId, info.StartDate);
             }
             }
 
 
             if (programInfo != null)
             if (programInfo != null)
             {
             {
-                CopyProgramInfoToTimerInfo(programInfo, timer);
+                CopyProgramInfoToTimerInfo(programInfo, info);
             }
             }
 
 
-            timer.IsManual = true;
-            _timerProvider.Add(timer);
+            info.IsManual = true;
+            _timerProvider.Add(info);
 
 
-            TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
+            TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(info));
 
 
-            return Task.FromResult(timer.Id);
+            return Task.FromResult(info.Id);
         }
         }
 
 
         public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
         public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
@@ -913,18 +911,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
                 var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
                 var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
 
 
-                List<ProgramInfo> programs;
-
                 if (epgChannel == null)
                 if (epgChannel == null)
                 {
                 {
                     _logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
                     _logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
-                    programs = new List<ProgramInfo>();
+                    continue;
                 }
                 }
-                else
-                {
-                    programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
+
+                List<ProgramInfo> programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
                            .ConfigureAwait(false)).ToList();
                            .ConfigureAwait(false)).ToList();
-                }
 
 
                 // Replace the value that came from the provider with a normalized value
                 // Replace the value that came from the provider with a normalized value
                 foreach (var program in programs)
                 foreach (var program in programs)
@@ -940,7 +934,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 }
                 }
             }
             }
 
 
-            return new List<ProgramInfo>();
+            return Enumerable.Empty<ProgramInfo>();
         }
         }
 
 
         private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
         private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
@@ -1292,7 +1286,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
                 _logger.LogInformation("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
                 _logger.LogInformation("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
 
 
-                _logger.LogInformation("Writing file to path: " + recordPath);
+                _logger.LogInformation("Writing file to: {Path}", recordPath);
 
 
                 Action onStarted = async () =>
                 Action onStarted = async () =>
                 {
                 {
@@ -1417,13 +1411,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
         private void TriggerRefresh(string path)
         private void TriggerRefresh(string path)
         {
         {
-            _logger.LogInformation("Triggering refresh on {path}", path);
+            _logger.LogInformation("Triggering refresh on {Path}", path);
 
 
             var item = GetAffectedBaseItem(Path.GetDirectoryName(path));
             var item = GetAffectedBaseItem(Path.GetDirectoryName(path));
 
 
             if (item != null)
             if (item != null)
             {
             {
-                _logger.LogInformation("Refreshing recording parent {path}", item.Path);
+                _logger.LogInformation("Refreshing recording parent {Path}", item.Path);
 
 
                 _providerManager.QueueRefresh(
                 _providerManager.QueueRefresh(
                     item.Id,
                     item.Id,
@@ -1458,7 +1452,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase))
                 if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     var parentItem = item.GetParent();
                     var parentItem = item.GetParent();
-                    if (parentItem != null && !(parentItem is AggregateFolder))
+                    if (parentItem != null && parentItem is not AggregateFolder)
                     {
                     {
                         item = parentItem;
                         item = parentItem;
                     }
                     }
@@ -1512,8 +1506,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
                 DeleteLibraryItemsForTimers(timersToDelete);
                 DeleteLibraryItemsForTimers(timersToDelete);
 
 
-                var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder;
-                if (librarySeries == null)
+                if (_libraryManager.FindByPath(seriesPath, true) is not Folder librarySeries)
                 {
                 {
                     return;
                     return;
                 }
                 }
@@ -1667,7 +1660,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
                 _logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
                 _logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
 
 
-                process.Exited += Process_Exited;
+                process.Exited += OnProcessExited;
                 process.Start();
                 process.Start();
             }
             }
             catch (Exception ex)
             catch (Exception ex)
@@ -1681,7 +1674,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return arguments.Replace("{path}", path, StringComparison.OrdinalIgnoreCase);
             return arguments.Replace("{path}", path, StringComparison.OrdinalIgnoreCase);
         }
         }
 
 
-        private void Process_Exited(object sender, EventArgs e)
+        private void OnProcessExited(object sender, EventArgs e)
         {
         {
             using (var process = (Process)sender)
             using (var process = (Process)sender)
             {
             {
@@ -2239,7 +2232,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             var enabledTimersForSeries = new List<TimerInfo>();
             var enabledTimersForSeries = new List<TimerInfo>();
             foreach (var timer in allTimers)
             foreach (var timer in allTimers)
             {
             {
-                var existingTimer = _timerProvider.GetTimer(timer.Id) 
+                var existingTimer = _timerProvider.GetTimer(timer.Id)
                     ?? (string.IsNullOrWhiteSpace(timer.ProgramId)
                     ?? (string.IsNullOrWhiteSpace(timer.ProgramId)
                         ? null
                         ? null
                         : _timerProvider.GetTimerByProgramId(timer.ProgramId));
                         : _timerProvider.GetTimerByProgramId(timer.ProgramId));

+ 0 - 5
Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs

@@ -319,11 +319,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     }
                     }
                 }
                 }
             }
             }
-            catch (ObjectDisposedException)
-            {
-                // TODO Investigate and properly fix.
-                // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
-            }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
                 _logger.LogError(ex, "Error reading ffmpeg recording log");
                 _logger.LogError(ex, "Error reading ffmpeg recording log");

+ 0 - 1
Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs

@@ -8,7 +8,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 {
 {
     internal class EpgChannelData
     internal class EpgChannelData
     {
     {
-
         private readonly Dictionary<string, ChannelInfo> _channelsById;
         private readonly Dictionary<string, ChannelInfo> _channelsById;
 
 
         private readonly Dictionary<string, ChannelInfo> _channelsByNumber;
         private readonly Dictionary<string, ChannelInfo> _channelsByNumber;

+ 1 - 1
Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs

@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         /// <summary>
         /// <summary>
         /// Records the specified media source.
         /// Records the specified media source.
         /// </summary>
         /// </summary>
-        Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
+        Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
 
 
         string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
         string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
     }
     }

+ 5 - 7
Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
 using System;
 using System;
@@ -23,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         {
         {
         }
         }
 
 
-        public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
+        public event EventHandler<GenericEventArgs<TimerInfo>>? TimerFired;
 
 
         public void RestartTimers()
         public void RestartTimers()
         {
         {
@@ -145,9 +143,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             }
             }
         }
         }
 
 
-        private void TimerCallback(object state)
+        private void TimerCallback(object? state)
         {
         {
-            var timerId = (string)state;
+            var timerId = (string?)state ?? throw new ArgumentNullException(nameof(state));
 
 
             var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
             var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
             if (timer != null)
             if (timer != null)
@@ -156,12 +154,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             }
             }
         }
         }
 
 
-        public TimerInfo GetTimer(string id)
+        public TimerInfo? GetTimer(string id)
         {
         {
             return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
             return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
         }
         }
 
 
-        public TimerInfo GetTimerByProgramId(string programId)
+        public TimerInfo? GetTimerByProgramId(string programId)
         {
         {
             return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
             return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
         }
         }

+ 113 - 509
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -14,8 +14,9 @@ using System.Text;
 using System.Text.Json;
 using System.Text.Json;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Common;
+using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
 using Jellyfin.Extensions.Json;
 using Jellyfin.Extensions.Json;
+using MediaBrowser.Common;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.Cryptography;
@@ -96,12 +97,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
             var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
 
 
             _logger.LogInformation("Channel Station ID is: {ChannelID}", channelId);
             _logger.LogInformation("Channel Station ID is: {ChannelID}", channelId);
-            var requestList = new List<ScheduleDirect.RequestScheduleForChannel>()
+            var requestList = new List<RequestScheduleForChannelDto>()
                 {
                 {
-                    new ScheduleDirect.RequestScheduleForChannel()
+                    new RequestScheduleForChannelDto()
                     {
                     {
-                        stationID = channelId,
-                        date = dates
+                        StationId = channelId,
+                        Date = dates
                     }
                     }
                 };
                 };
 
 
@@ -113,61 +114,61 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             options.Headers.TryAddWithoutValidation("token", token);
             options.Headers.TryAddWithoutValidation("token", token);
             using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
             using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
             await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
             await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
-            var dailySchedules = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Day>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+            var dailySchedules = await JsonSerializer.DeserializeAsync<List<DayDto>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
             _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
             _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
 
 
             using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
             using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
             programRequestOptions.Headers.TryAddWithoutValidation("token", token);
             programRequestOptions.Headers.TryAddWithoutValidation("token", token);
 
 
-            var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
+            var programsID = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
             programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
             programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
 
 
             using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
             using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
             await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
             await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
-            var programDetails = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
-            var programDict = programDetails.ToDictionary(p => p.programID, y => y);
+            var programDetails = await JsonSerializer.DeserializeAsync<List<ProgramDetailsDto>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+            var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y);
 
 
             var programIdsWithImages = programDetails
             var programIdsWithImages = programDetails
-                .Where(p => p.hasImageArtwork).Select(p => p.programID)
+                .Where(p => p.HasImageArtwork).Select(p => p.ProgramId)
                 .ToList();
                 .ToList();
 
 
             var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
             var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
 
 
             var programsInfo = new List<ProgramInfo>();
             var programsInfo = new List<ProgramInfo>();
-            foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
+            foreach (ProgramDto schedule in dailySchedules.SelectMany(d => d.Programs))
             {
             {
                 // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
                 // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
                 //              " which corresponds to channel " + channelNumber + " and program id " +
                 //              " which corresponds to channel " + channelNumber + " and program id " +
-                //              schedule.programID + " which says it has images? " +
-                //              programDict[schedule.programID].hasImageArtwork);
+                //              schedule.ProgramId + " which says it has images? " +
+                //              programDict[schedule.ProgramId].hasImageArtwork);
 
 
                 if (images != null)
                 if (images != null)
                 {
                 {
-                    var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
+                    var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]);
                     if (imageIndex > -1)
                     if (imageIndex > -1)
                     {
                     {
-                        var programEntry = programDict[schedule.programID];
+                        var programEntry = programDict[schedule.ProgramId];
 
 
-                        var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
-                        var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase));
-                        var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase));
+                        var allImages = images[imageIndex].Data ?? new List<ImageDataDto>();
+                        var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase));
+                        var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase));
 
 
                         const double DesiredAspect = 2.0 / 3;
                         const double DesiredAspect = 2.0 / 3;
 
 
-                        programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
+                        programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
                                                     GetProgramImage(ApiUrl, allImages, true, DesiredAspect);
                                                     GetProgramImage(ApiUrl, allImages, true, DesiredAspect);
 
 
                         const double WideAspect = 16.0 / 9;
                         const double WideAspect = 16.0 / 9;
 
 
-                        programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
+                        programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
 
 
                         // Don't supply the same image twice
                         // Don't supply the same image twice
-                        if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal))
+                        if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
                         {
                         {
-                            programEntry.thumbImage = null;
+                            programEntry.ThumbImage = null;
                         }
                         }
 
 
-                        programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
+                        programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
 
 
                         // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
                         // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
                         //    GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
                         //    GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
@@ -176,15 +177,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                     }
                     }
                 }
                 }
 
 
-                programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
+                programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.ProgramId]));
             }
             }
 
 
             return programsInfo;
             return programsInfo;
         }
         }
 
 
-        private static int GetSizeOrder(ScheduleDirect.ImageData image)
+        private static int GetSizeOrder(ImageDataDto image)
         {
         {
-            if (int.TryParse(image.height, out int value))
+            if (int.TryParse(image.Height, out int value))
             {
             {
                 return value;
                 return value;
             }
             }
@@ -192,53 +193,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             return 0;
             return 0;
         }
         }
 
 
-        private static string GetChannelNumber(ScheduleDirect.Map map)
+        private static string GetChannelNumber(MapDto map)
         {
         {
-            var channelNumber = map.logicalChannelNumber;
+            var channelNumber = map.LogicalChannelNumber;
 
 
             if (string.IsNullOrWhiteSpace(channelNumber))
             if (string.IsNullOrWhiteSpace(channelNumber))
             {
             {
-                channelNumber = map.channel;
+                channelNumber = map.Channel;
             }
             }
 
 
             if (string.IsNullOrWhiteSpace(channelNumber))
             if (string.IsNullOrWhiteSpace(channelNumber))
             {
             {
-                channelNumber = map.atscMajor + "." + map.atscMinor;
+                channelNumber = map.AtscMajor + "." + map.AtscMinor;
             }
             }
 
 
             return channelNumber.TrimStart('0');
             return channelNumber.TrimStart('0');
         }
         }
 
 
-        private static bool IsMovie(ScheduleDirect.ProgramDetails programInfo)
+        private static bool IsMovie(ProgramDetailsDto programInfo)
         {
         {
-            return string.Equals(programInfo.entityType, "movie", StringComparison.OrdinalIgnoreCase);
+            return string.Equals(programInfo.EntityType, "movie", StringComparison.OrdinalIgnoreCase);
         }
         }
 
 
-        private ProgramInfo GetProgram(string channelId, ScheduleDirect.Program programInfo, ScheduleDirect.ProgramDetails details)
+        private ProgramInfo GetProgram(string channelId, ProgramDto programInfo, ProgramDetailsDto details)
         {
         {
-            var startAt = GetDate(programInfo.airDateTime);
-            var endAt = startAt.AddSeconds(programInfo.duration);
+            var startAt = GetDate(programInfo.AirDateTime);
+            var endAt = startAt.AddSeconds(programInfo.Duration);
             var audioType = ProgramAudio.Stereo;
             var audioType = ProgramAudio.Stereo;
 
 
-            var programId = programInfo.programID ?? string.Empty;
+            var programId = programInfo.ProgramId ?? string.Empty;
 
 
             string newID = programId + "T" + startAt.Ticks + "C" + channelId;
             string newID = programId + "T" + startAt.Ticks + "C" + channelId;
 
 
-            if (programInfo.audioProperties != null)
+            if (programInfo.AudioProperties != null)
             {
             {
-                if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
+                if (programInfo.AudioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
                 {
                 {
                     audioType = ProgramAudio.Atmos;
                     audioType = ProgramAudio.Atmos;
                 }
                 }
-                else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
+                else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
                 {
                 {
                     audioType = ProgramAudio.DolbyDigital;
                     audioType = ProgramAudio.DolbyDigital;
                 }
                 }
-                else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
+                else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
                 {
                 {
                     audioType = ProgramAudio.DolbyDigital;
                     audioType = ProgramAudio.DolbyDigital;
                 }
                 }
-                else if (programInfo.audioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
+                else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
                 {
                 {
                     audioType = ProgramAudio.Stereo;
                     audioType = ProgramAudio.Stereo;
                 }
                 }
@@ -249,9 +250,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             }
             }
 
 
             string episodeTitle = null;
             string episodeTitle = null;
-            if (details.episodeTitle150 != null)
+            if (details.EpisodeTitle150 != null)
             {
             {
-                episodeTitle = details.episodeTitle150;
+                episodeTitle = details.EpisodeTitle150;
             }
             }
 
 
             var info = new ProgramInfo
             var info = new ProgramInfo
@@ -260,22 +261,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 Id = newID,
                 Id = newID,
                 StartDate = startAt,
                 StartDate = startAt,
                 EndDate = endAt,
                 EndDate = endAt,
-                Name = details.titles[0].title120 ?? "Unknown",
+                Name = details.Titles[0].Title120 ?? "Unknown",
                 OfficialRating = null,
                 OfficialRating = null,
                 CommunityRating = null,
                 CommunityRating = null,
                 EpisodeTitle = episodeTitle,
                 EpisodeTitle = episodeTitle,
                 Audio = audioType,
                 Audio = audioType,
                 // IsNew = programInfo.@new ?? false,
                 // IsNew = programInfo.@new ?? false,
-                IsRepeat = programInfo.@new == null,
-                IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase),
-                ImageUrl = details.primaryImage,
-                ThumbImageUrl = details.thumbImage,
-                IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
-                IsSports = string.Equals(details.entityType, "sports", StringComparison.OrdinalIgnoreCase),
+                IsRepeat = programInfo.New == null,
+                IsSeries = string.Equals(details.EntityType, "episode", StringComparison.OrdinalIgnoreCase),
+                ImageUrl = details.PrimaryImage,
+                ThumbImageUrl = details.ThumbImage,
+                IsKids = string.Equals(details.Audience, "children", StringComparison.OrdinalIgnoreCase),
+                IsSports = string.Equals(details.EntityType, "sports", StringComparison.OrdinalIgnoreCase),
                 IsMovie = IsMovie(details),
                 IsMovie = IsMovie(details),
-                Etag = programInfo.md5,
-                IsLive = string.Equals(programInfo.liveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
-                IsPremiere = programInfo.premiere || (programInfo.isPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
+                Etag = programInfo.Md5,
+                IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
+                IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
             };
             };
 
 
             var showId = programId;
             var showId = programId;
@@ -298,15 +299,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
 
             info.ShowId = showId;
             info.ShowId = showId;
 
 
-            if (programInfo.videoProperties != null)
+            if (programInfo.VideoProperties != null)
             {
             {
-                info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
-                info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
+                info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
+                info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
             }
             }
 
 
-            if (details.contentRating != null && details.contentRating.Count > 0)
+            if (details.ContentRating != null && details.ContentRating.Count > 0)
             {
             {
-                info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-", StringComparison.Ordinal)
+                info.OfficialRating = details.ContentRating[0].Code.Replace("TV", "TV-", StringComparison.Ordinal)
                     .Replace("--", "-", StringComparison.Ordinal);
                     .Replace("--", "-", StringComparison.Ordinal);
 
 
                 var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
                 var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
@@ -316,15 +317,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 }
                 }
             }
             }
 
 
-            if (details.descriptions != null)
+            if (details.Descriptions != null)
             {
             {
-                if (details.descriptions.description1000 != null && details.descriptions.description1000.Count > 0)
+                if (details.Descriptions.Description1000 != null && details.Descriptions.Description1000.Count > 0)
                 {
                 {
-                    info.Overview = details.descriptions.description1000[0].description;
+                    info.Overview = details.Descriptions.Description1000[0].Description;
                 }
                 }
-                else if (details.descriptions.description100 != null && details.descriptions.description100.Count > 0)
+                else if (details.Descriptions.Description100 != null && details.Descriptions.Description100.Count > 0)
                 {
                 {
-                    info.Overview = details.descriptions.description100[0].description;
+                    info.Overview = details.Descriptions.Description100[0].Description;
                 }
                 }
             }
             }
 
 
@@ -334,18 +335,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
 
                 info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId;
                 info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId;
 
 
-                if (details.metadata != null)
+                if (details.Metadata != null)
                 {
                 {
-                    foreach (var metadataProgram in details.metadata)
+                    foreach (var metadataProgram in details.Metadata)
                     {
                     {
                         var gracenote = metadataProgram.Gracenote;
                         var gracenote = metadataProgram.Gracenote;
                         if (gracenote != null)
                         if (gracenote != null)
                         {
                         {
-                            info.SeasonNumber = gracenote.season;
+                            info.SeasonNumber = gracenote.Season;
 
 
-                            if (gracenote.episode > 0)
+                            if (gracenote.Episode > 0)
                             {
                             {
-                                info.EpisodeNumber = gracenote.episode;
+                                info.EpisodeNumber = gracenote.Episode;
                             }
                             }
 
 
                             break;
                             break;
@@ -354,25 +355,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 }
                 }
             }
             }
 
 
-            if (!string.IsNullOrWhiteSpace(details.originalAirDate))
+            if (!string.IsNullOrWhiteSpace(details.OriginalAirDate))
             {
             {
-                info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture);
+                info.OriginalAirDate = DateTime.Parse(details.OriginalAirDate, CultureInfo.InvariantCulture);
                 info.ProductionYear = info.OriginalAirDate.Value.Year;
                 info.ProductionYear = info.OriginalAirDate.Value.Year;
             }
             }
 
 
-            if (details.movie != null)
+            if (details.Movie != null)
             {
             {
-                if (!string.IsNullOrEmpty(details.movie.year)
-                    && int.TryParse(details.movie.year, out int year))
+                if (!string.IsNullOrEmpty(details.Movie.Year)
+                    && int.TryParse(details.Movie.Year, out int year))
                 {
                 {
                     info.ProductionYear = year;
                     info.ProductionYear = year;
                 }
                 }
             }
             }
 
 
-            if (details.genres != null)
+            if (details.Genres != null)
             {
             {
-                info.Genres = details.genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
-                info.IsNews = details.genres.Contains("news", StringComparer.OrdinalIgnoreCase);
+                info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
+                info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase);
 
 
                 if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase))
                 if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase))
                 {
                 {
@@ -395,11 +396,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             return date;
             return date;
         }
         }
 
 
-        private string GetProgramImage(string apiUrl, IEnumerable<ScheduleDirect.ImageData> images, bool returnDefaultImage, double desiredAspect)
+        private string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, bool returnDefaultImage, double desiredAspect)
         {
         {
             var match = images
             var match = images
                 .OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
                 .OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
-                .ThenByDescending(GetSizeOrder)
+                .ThenByDescending(i => GetSizeOrder(i))
                 .FirstOrDefault();
                 .FirstOrDefault();
 
 
             if (match == null)
             if (match == null)
@@ -407,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 return null;
                 return null;
             }
             }
 
 
-            var uri = match.uri;
+            var uri = match.Uri;
 
 
             if (string.IsNullOrWhiteSpace(uri))
             if (string.IsNullOrWhiteSpace(uri))
             {
             {
@@ -423,19 +424,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             }
             }
         }
         }
 
 
-        private static double GetAspectRatio(ScheduleDirect.ImageData i)
+        private static double GetAspectRatio(ImageDataDto i)
         {
         {
             int width = 0;
             int width = 0;
             int height = 0;
             int height = 0;
 
 
-            if (!string.IsNullOrWhiteSpace(i.width))
+            if (!string.IsNullOrWhiteSpace(i.Width))
             {
             {
-                int.TryParse(i.width, out width);
+                _ = int.TryParse(i.Width, out width);
             }
             }
 
 
-            if (!string.IsNullOrWhiteSpace(i.height))
+            if (!string.IsNullOrWhiteSpace(i.Height))
             {
             {
-                int.TryParse(i.height, out height);
+                _ = int.TryParse(i.Height, out height);
             }
             }
 
 
             if (height == 0 || width == 0)
             if (height == 0 || width == 0)
@@ -448,14 +449,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             return result;
             return result;
         }
         }
 
 
-        private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms(
+        private async Task<List<ShowImagesDto>> GetImageForPrograms(
             ListingsProviderInfo info,
             ListingsProviderInfo info,
             IReadOnlyList<string> programIds,
             IReadOnlyList<string> programIds,
             CancellationToken cancellationToken)
             CancellationToken cancellationToken)
         {
         {
             if (programIds.Count == 0)
             if (programIds.Count == 0)
             {
             {
-                return new List<ScheduleDirect.ShowImages>();
+                return new List<ShowImagesDto>();
             }
             }
 
 
             StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
             StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
@@ -479,13 +480,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             {
             {
                 using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
                 using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
                 await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
                 await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
-                return await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ShowImages>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+                return await JsonSerializer.DeserializeAsync<List<ShowImagesDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
                 _logger.LogError(ex, "Error getting image info from schedules direct");
                 _logger.LogError(ex, "Error getting image info from schedules direct");
 
 
-                return new List<ScheduleDirect.ShowImages>();
+                return new List<ShowImagesDto>();
             }
             }
         }
         }
 
 
@@ -508,18 +509,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
                 using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
                 await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
                 await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 
 
-                var root = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Headends>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+                var root = await JsonSerializer.DeserializeAsync<List<HeadendsDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
 
 
                 if (root != null)
                 if (root != null)
                 {
                 {
-                    foreach (ScheduleDirect.Headends headend in root)
+                    foreach (HeadendsDto headend in root)
                     {
                     {
-                        foreach (ScheduleDirect.Lineup lineup in headend.lineups)
+                        foreach (LineupDto lineup in headend.Lineups)
                         {
                         {
                             lineups.Add(new NameIdPair
                             lineups.Add(new NameIdPair
                             {
                             {
-                                Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name,
-                                Id = lineup.uri.Substring(18)
+                                Name = string.IsNullOrWhiteSpace(lineup.Name) ? lineup.Lineup : lineup.Name,
+                                Id = lineup.Uri[18..]
                             });
                             });
                         }
                         }
                     }
                     }
@@ -649,14 +650,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
             using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
             response.EnsureSuccessStatusCode();
             response.EnsureSuccessStatusCode();
             await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
             await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
-            var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Token>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
-            if (string.Equals(root.message, "OK", StringComparison.Ordinal))
+            var root = await JsonSerializer.DeserializeAsync<TokenDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+            if (string.Equals(root.Message, "OK", StringComparison.Ordinal))
             {
             {
-                _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
-                return root.token;
+                _logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token);
+                return root.Token;
             }
             }
 
 
-            throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
+            throw new Exception("Could not authenticate with Schedules Direct Error: " + root.Message);
         }
         }
 
 
         private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
         private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
@@ -705,9 +706,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 httpResponse.EnsureSuccessStatusCode();
                 httpResponse.EnsureSuccessStatusCode();
                 await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
                 await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
                 using var response = httpResponse.Content;
                 using var response = httpResponse.Content;
-                var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Lineups>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+                var root = await JsonSerializer.DeserializeAsync<LineupsDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
 
 
-                return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
+                return root.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase));
             }
             }
             catch (HttpRequestException ex)
             catch (HttpRequestException ex)
             {
             {
@@ -777,35 +778,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
 
             using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
             using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
             await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
             await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
-            var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Channel>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
-            _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
+            var root = await JsonSerializer.DeserializeAsync<ChannelDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+            _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count);
             _logger.LogInformation("Mapping Stations to Channel");
             _logger.LogInformation("Mapping Stations to Channel");
 
 
-            var allStations = root.stations ?? new List<ScheduleDirect.Station>();
+            var allStations = root.Stations ?? new List<StationDto>();
 
 
-            var map = root.map;
+            var map = root.Map;
             var list = new List<ChannelInfo>(map.Count);
             var list = new List<ChannelInfo>(map.Count);
             foreach (var channel in map)
             foreach (var channel in map)
             {
             {
                 var channelNumber = GetChannelNumber(channel);
                 var channelNumber = GetChannelNumber(channel);
 
 
-                var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase))
-                    ?? new ScheduleDirect.Station
+                var station = allStations.Find(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase))
+                    ?? new StationDto
                     {
                     {
-                        stationID = channel.stationID
+                        StationId = channel.StationId
                     };
                     };
 
 
                 var channelInfo = new ChannelInfo
                 var channelInfo = new ChannelInfo
                 {
                 {
-                    Id = station.stationID,
-                    CallSign = station.callsign,
+                    Id = station.StationId,
+                    CallSign = station.Callsign,
                     Number = channelNumber,
                     Number = channelNumber,
-                    Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
+                    Name = string.IsNullOrWhiteSpace(station.Name) ? channelNumber : station.Name
                 };
                 };
 
 
-                if (station.logo != null)
+                if (station.Logo != null)
                 {
                 {
-                    channelInfo.ImageUrl = station.logo.URL;
+                    channelInfo.ImageUrl = station.Logo.Url;
                 }
                 }
 
 
                 list.Add(channelInfo);
                 list.Add(channelInfo);
@@ -818,402 +819,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings
         {
         {
             return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
             return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
         }
         }
-
-        public class ScheduleDirect
-        {
-            public class Token
-            {
-                public int code { get; set; }
-
-                public string message { get; set; }
-
-                public string serverID { get; set; }
-
-                public string token { get; set; }
-            }
-
-            public class Lineup
-            {
-                public string lineup { get; set; }
-
-                public string name { get; set; }
-
-                public string transport { get; set; }
-
-                public string location { get; set; }
-
-                public string uri { get; set; }
-            }
-
-            public class Lineups
-            {
-                public int code { get; set; }
-
-                public string serverID { get; set; }
-
-                public string datetime { get; set; }
-
-                public List<Lineup> lineups { get; set; }
-            }
-
-            public class Headends
-            {
-                public string headend { get; set; }
-
-                public string transport { get; set; }
-
-                public string location { get; set; }
-
-                public List<Lineup> lineups { get; set; }
-            }
-
-            public class Map
-            {
-                public string stationID { get; set; }
-
-                public string channel { get; set; }
-
-                public string logicalChannelNumber { get; set; }
-
-                public int uhfVhf { get; set; }
-
-                public int atscMajor { get; set; }
-
-                public int atscMinor { get; set; }
-            }
-
-            public class Broadcaster
-            {
-                public string city { get; set; }
-
-                public string state { get; set; }
-
-                public string postalcode { get; set; }
-
-                public string country { get; set; }
-            }
-
-            public class Logo
-            {
-                public string URL { get; set; }
-
-                public int height { get; set; }
-
-                public int width { get; set; }
-
-                public string md5 { get; set; }
-            }
-
-            public class Station
-            {
-                public string stationID { get; set; }
-
-                public string name { get; set; }
-
-                public string callsign { get; set; }
-
-                public List<string> broadcastLanguage { get; set; }
-
-                public List<string> descriptionLanguage { get; set; }
-
-                public Broadcaster broadcaster { get; set; }
-
-                public string affiliate { get; set; }
-
-                public Logo logo { get; set; }
-
-                public bool? isCommercialFree { get; set; }
-            }
-
-            public class Metadata
-            {
-                public string lineup { get; set; }
-
-                public string modified { get; set; }
-
-                public string transport { get; set; }
-            }
-
-            public class Channel
-            {
-                public List<Map> map { get; set; }
-
-                public List<Station> stations { get; set; }
-
-                public Metadata metadata { get; set; }
-            }
-
-            public class RequestScheduleForChannel
-            {
-                public string stationID { get; set; }
-
-                public List<string> date { get; set; }
-            }
-
-            public class Rating
-            {
-                public string body { get; set; }
-
-                public string code { get; set; }
-            }
-
-            public class Multipart
-            {
-                public int partNumber { get; set; }
-
-                public int totalParts { get; set; }
-            }
-
-            public class Program
-            {
-                public string programID { get; set; }
-
-                public string airDateTime { get; set; }
-
-                public int duration { get; set; }
-
-                public string md5 { get; set; }
-
-                public List<string> audioProperties { get; set; }
-
-                public List<string> videoProperties { get; set; }
-
-                public List<Rating> ratings { get; set; }
-
-                public bool? @new { get; set; }
-
-                public Multipart multipart { get; set; }
-
-                public string liveTapeDelay { get; set; }
-
-                public bool premiere { get; set; }
-
-                public bool repeat { get; set; }
-
-                public string isPremiereOrFinale { get; set; }
-            }
-
-            public class MetadataSchedule
-            {
-                public string modified { get; set; }
-
-                public string md5 { get; set; }
-
-                public string startDate { get; set; }
-
-                public string endDate { get; set; }
-
-                public int days { get; set; }
-            }
-
-            public class Day
-            {
-                public string stationID { get; set; }
-
-                public List<Program> programs { get; set; }
-
-                public MetadataSchedule metadata { get; set; }
-
-                public Day()
-                {
-                    programs = new List<Program>();
-                }
-            }
-
-            public class Title
-            {
-                public string title120 { get; set; }
-            }
-
-            public class EventDetails
-            {
-                public string subType { get; set; }
-            }
-
-            public class Description100
-            {
-                public string descriptionLanguage { get; set; }
-
-                public string description { get; set; }
-            }
-
-            public class Description1000
-            {
-                public string descriptionLanguage { get; set; }
-
-                public string description { get; set; }
-            }
-
-            public class DescriptionsProgram
-            {
-                public List<Description100> description100 { get; set; }
-
-                public List<Description1000> description1000 { get; set; }
-            }
-
-            public class Gracenote
-            {
-                public int season { get; set; }
-
-                public int episode { get; set; }
-            }
-
-            public class MetadataPrograms
-            {
-                public Gracenote Gracenote { get; set; }
-            }
-
-            public class ContentRating
-            {
-                public string body { get; set; }
-
-                public string code { get; set; }
-            }
-
-            public class Cast
-            {
-                public string billingOrder { get; set; }
-
-                public string role { get; set; }
-
-                public string nameId { get; set; }
-
-                public string personId { get; set; }
-
-                public string name { get; set; }
-
-                public string characterName { get; set; }
-            }
-
-            public class Crew
-            {
-                public string billingOrder { get; set; }
-
-                public string role { get; set; }
-
-                public string nameId { get; set; }
-
-                public string personId { get; set; }
-
-                public string name { get; set; }
-            }
-
-            public class QualityRating
-            {
-                public string ratingsBody { get; set; }
-
-                public string rating { get; set; }
-
-                public string minRating { get; set; }
-
-                public string maxRating { get; set; }
-
-                public string increment { get; set; }
-            }
-
-            public class Movie
-            {
-                public string year { get; set; }
-
-                public int duration { get; set; }
-
-                public List<QualityRating> qualityRating { get; set; }
-            }
-
-            public class Recommendation
-            {
-                public string programID { get; set; }
-
-                public string title120 { get; set; }
-            }
-
-            public class ProgramDetails
-            {
-                public string audience { get; set; }
-
-                public string programID { get; set; }
-
-                public List<Title> titles { get; set; }
-
-                public EventDetails eventDetails { get; set; }
-
-                public DescriptionsProgram descriptions { get; set; }
-
-                public string originalAirDate { get; set; }
-
-                public List<string> genres { get; set; }
-
-                public string episodeTitle150 { get; set; }
-
-                public List<MetadataPrograms> metadata { get; set; }
-
-                public List<ContentRating> contentRating { get; set; }
-
-                public List<Cast> cast { get; set; }
-
-                public List<Crew> crew { get; set; }
-
-                public string entityType { get; set; }
-
-                public string showType { get; set; }
-
-                public bool hasImageArtwork { get; set; }
-
-                public string primaryImage { get; set; }
-
-                public string thumbImage { get; set; }
-
-                public string backdropImage { get; set; }
-
-                public string bannerImage { get; set; }
-
-                public string imageID { get; set; }
-
-                public string md5 { get; set; }
-
-                public List<string> contentAdvisory { get; set; }
-
-                public Movie movie { get; set; }
-
-                public List<Recommendation> recommendations { get; set; }
-            }
-
-            public class Caption
-            {
-                public string content { get; set; }
-
-                public string lang { get; set; }
-            }
-
-            public class ImageData
-            {
-                public string width { get; set; }
-
-                public string height { get; set; }
-
-                public string uri { get; set; }
-
-                public string size { get; set; }
-
-                public string aspect { get; set; }
-
-                public string category { get; set; }
-
-                public string text { get; set; }
-
-                public string primary { get; set; }
-
-                public string tier { get; set; }
-
-                public Caption caption { get; set; }
-            }
-
-            public class ShowImages
-            {
-                public string programID { get; set; }
-
-                public List<ImageData> data { get; set; }
-            }
-        }
     }
     }
 }
 }

+ 36 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs

@@ -0,0 +1,36 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Broadcaster dto.
+    /// </summary>
+    public class BroadcasterDto
+    {
+        /// <summary>
+        /// Gets or sets the city.
+        /// </summary>
+        [JsonPropertyName("city")]
+        public string City { get; set; }
+
+        /// <summary>
+        /// Gets or sets the state.
+        /// </summary>
+        [JsonPropertyName("state")]
+        public string State { get; set; }
+
+        /// <summary>
+        /// Gets or sets the postal code.
+        /// </summary>
+        [JsonPropertyName("postalCode")]
+        public string Postalcode { get; set; }
+
+        /// <summary>
+        /// Gets or sets the country.
+        /// </summary>
+        [JsonPropertyName("country")]
+        public string Country { get; set; }
+    }
+}

+ 24 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs

@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Caption dto.
+    /// </summary>
+    public class CaptionDto
+    {
+        /// <summary>
+        /// Gets or sets the content.
+        /// </summary>
+        [JsonPropertyName("content")]
+        public string Content { get; set; }
+
+        /// <summary>
+        /// Gets or sets the lang.
+        /// </summary>
+        [JsonPropertyName("lang")]
+        public string Lang { get; set; }
+    }
+}

+ 48 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs

@@ -0,0 +1,48 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Cast dto.
+    /// </summary>
+    public class CastDto
+    {
+        /// <summary>
+        /// Gets or sets the billing order.
+        /// </summary>
+        [JsonPropertyName("billingOrder")]
+        public string BillingOrder { get; set; }
+
+        /// <summary>
+        /// Gets or sets the role.
+        /// </summary>
+        [JsonPropertyName("role")]
+        public string Role { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name id.
+        /// </summary>
+        [JsonPropertyName("nameId")]
+        public string NameId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the person id.
+        /// </summary>
+        [JsonPropertyName("personId")]
+        public string PersonId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        [JsonPropertyName("name")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the character name.
+        /// </summary>
+        [JsonPropertyName("characterName")]
+        public string CharacterName { get; set; }
+    }
+}

+ 31 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs

@@ -0,0 +1,31 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Channel dto.
+    /// </summary>
+    public class ChannelDto
+    {
+        /// <summary>
+        /// Gets or sets the list of maps.
+        /// </summary>
+        [JsonPropertyName("map")]
+        public List<MapDto> Map { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of stations.
+        /// </summary>
+        [JsonPropertyName("stations")]
+        public List<StationDto> Stations { get; set; }
+
+        /// <summary>
+        /// Gets or sets the metadata.
+        /// </summary>
+        [JsonPropertyName("metadata")]
+        public MetadataDto Metadata { get; set; }
+    }
+}

+ 24 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs

@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Content rating dto.
+    /// </summary>
+    public class ContentRatingDto
+    {
+        /// <summary>
+        /// Gets or sets the body.
+        /// </summary>
+        [JsonPropertyName("body")]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// Gets or sets the code.
+        /// </summary>
+        [JsonPropertyName("code")]
+        public string Code { get; set; }
+    }
+}

+ 42 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs

@@ -0,0 +1,42 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Crew dto.
+    /// </summary>
+    public class CrewDto
+    {
+        /// <summary>
+        /// Gets or sets the billing order.
+        /// </summary>
+        [JsonPropertyName("billingOrder")]
+        public string BillingOrder { get; set; }
+
+        /// <summary>
+        /// Gets or sets the role.
+        /// </summary>
+        [JsonPropertyName("role")]
+        public string Role { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name id.
+        /// </summary>
+        [JsonPropertyName("nameId")]
+        public string NameId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the person id.
+        /// </summary>
+        [JsonPropertyName("personId")]
+        public string PersonId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        [JsonPropertyName("name")]
+        public string Name { get; set; }
+    }
+}

+ 39 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs

@@ -0,0 +1,39 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Day dto.
+    /// </summary>
+    public class DayDto
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DayDto"/> class.
+        /// </summary>
+        public DayDto()
+        {
+            Programs = new List<ProgramDto>();
+        }
+
+        /// <summary>
+        /// Gets or sets the station id.
+        /// </summary>
+        [JsonPropertyName("stationID")]
+        public string StationId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of programs.
+        /// </summary>
+        [JsonPropertyName("programs")]
+        public List<ProgramDto> Programs { get; set; }
+
+        /// <summary>
+        /// Gets or sets the metadata schedule.
+        /// </summary>
+        [JsonPropertyName("metadata")]
+        public MetadataScheduleDto Metadata { get; set; }
+    }
+}

+ 24 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs

@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Description 1_000 dto.
+    /// </summary>
+    public class Description1000Dto
+    {
+        /// <summary>
+        /// Gets or sets the description language.
+        /// </summary>
+        [JsonPropertyName("descriptionLanguage")]
+        public string DescriptionLanguage { get; set; }
+
+        /// <summary>
+        /// Gets or sets the description.
+        /// </summary>
+        [JsonPropertyName("description")]
+        public string Description { get; set; }
+    }
+}

+ 24 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs

@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Description 100 dto.
+    /// </summary>
+    public class Description100Dto
+    {
+        /// <summary>
+        /// Gets or sets the description language.
+        /// </summary>
+        [JsonPropertyName("descriptionLanguage")]
+        public string DescriptionLanguage { get; set; }
+
+        /// <summary>
+        /// Gets or sets the description.
+        /// </summary>
+        [JsonPropertyName("description")]
+        public string Description { get; set; }
+    }
+}

+ 25 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs

@@ -0,0 +1,25 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Descriptions program dto.
+    /// </summary>
+    public class DescriptionsProgramDto
+    {
+        /// <summary>
+        /// Gets or sets the list of description 100.
+        /// </summary>
+        [JsonPropertyName("description100")]
+        public List<Description100Dto> Description100 { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of description1000.
+        /// </summary>
+        [JsonPropertyName("description1000")]
+        public List<Description1000Dto> Description1000 { get; set; }
+    }
+}

+ 18 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs

@@ -0,0 +1,18 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Event details dto.
+    /// </summary>
+    public class EventDetailsDto
+    {
+        /// <summary>
+        /// Gets or sets the sub type.
+        /// </summary>
+        [JsonPropertyName("subType")]
+        public string SubType { get; set; }
+    }
+}

+ 24 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs

@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Gracenote dto.
+    /// </summary>
+    public class GracenoteDto
+    {
+        /// <summary>
+        /// Gets or sets the season.
+        /// </summary>
+        [JsonPropertyName("season")]
+        public int Season { get; set; }
+
+        /// <summary>
+        /// Gets or sets the episode.
+        /// </summary>
+        [JsonPropertyName("episode")]
+        public int Episode { get; set; }
+    }
+}

+ 37 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs

@@ -0,0 +1,37 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Headends dto.
+    /// </summary>
+    public class HeadendsDto
+    {
+        /// <summary>
+        /// Gets or sets the headend.
+        /// </summary>
+        [JsonPropertyName("headend")]
+        public string Headend { get; set; }
+
+        /// <summary>
+        /// Gets or sets the transport.
+        /// </summary>
+        [JsonPropertyName("transport")]
+        public string Transport { get; set; }
+
+        /// <summary>
+        /// Gets or sets the location.
+        /// </summary>
+        [JsonPropertyName("location")]
+        public string Location { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of lineups.
+        /// </summary>
+        [JsonPropertyName("lineups")]
+        public List<LineupDto> Lineups { get; set; }
+    }
+}

+ 72 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs

@@ -0,0 +1,72 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Image data dto.
+    /// </summary>
+    public class ImageDataDto
+    {
+        /// <summary>
+        /// Gets or sets the width.
+        /// </summary>
+        [JsonPropertyName("width")]
+        public string Width { get; set; }
+
+        /// <summary>
+        /// Gets or sets the height.
+        /// </summary>
+        [JsonPropertyName("height")]
+        public string Height { get; set; }
+
+        /// <summary>
+        /// Gets or sets the uri.
+        /// </summary>
+        [JsonPropertyName("uri")]
+        public string Uri { get; set; }
+
+        /// <summary>
+        /// Gets or sets the size.
+        /// </summary>
+        [JsonPropertyName("size")]
+        public string Size { get; set; }
+
+        /// <summary>
+        /// Gets or sets the aspect.
+        /// </summary>
+        [JsonPropertyName("aspect")]
+        public string aspect { get; set; }
+
+        /// <summary>
+        /// Gets or sets the category.
+        /// </summary>
+        [JsonPropertyName("category")]
+        public string Category { get; set; }
+
+        /// <summary>
+        /// Gets or sets the text.
+        /// </summary>
+        [JsonPropertyName("text")]
+        public string Text { get; set; }
+
+        /// <summary>
+        /// Gets or sets the primary.
+        /// </summary>
+        [JsonPropertyName("primary")]
+        public string Primary { get; set; }
+
+        /// <summary>
+        /// Gets or sets the tier.
+        /// </summary>
+        [JsonPropertyName("tier")]
+        public string Tier { get; set; }
+
+        /// <summary>
+        /// Gets or sets the caption.
+        /// </summary>
+        [JsonPropertyName("caption")]
+        public CaptionDto Caption { get; set; }
+    }
+}

+ 42 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs

@@ -0,0 +1,42 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// The lineup dto.
+    /// </summary>
+    public class LineupDto
+    {
+        /// <summary>
+        /// Gets or sets the linup.
+        /// </summary>
+        [JsonPropertyName("lineup")]
+        public string Lineup { get; set; }
+
+        /// <summary>
+        /// Gets or sets the lineup name.
+        /// </summary>
+        [JsonPropertyName("name")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the transport.
+        /// </summary>
+        [JsonPropertyName("transport")]
+        public string Transport { get; set; }
+
+        /// <summary>
+        /// Gets or sets the location.
+        /// </summary>
+        [JsonPropertyName("location")]
+        public string Location { get; set; }
+
+        /// <summary>
+        /// Gets or sets the uri.
+        /// </summary>
+        [JsonPropertyName("uri")]
+        public string Uri { get; set; }
+    }
+}

+ 37 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs

@@ -0,0 +1,37 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Lineups dto.
+    /// </summary>
+    public class LineupsDto
+    {
+        /// <summary>
+        /// Gets or sets the response code.
+        /// </summary>
+        [JsonPropertyName("code")]
+        public int Code { get; set; }
+
+        /// <summary>
+        /// Gets or sets the server id.
+        /// </summary>
+        [JsonPropertyName("serverID")]
+        public string ServerId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the datetime.
+        /// </summary>
+        [JsonPropertyName("datetime")]
+        public string Datetime { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of lineups.
+        /// </summary>
+        [JsonPropertyName("lineups")]
+        public List<LineupDto> Lineups { get; set; }
+    }
+}

+ 36 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs

@@ -0,0 +1,36 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Logo dto.
+    /// </summary>
+    public class LogoDto
+    {
+        /// <summary>
+        /// Gets or sets the url.
+        /// </summary>
+        [JsonPropertyName("URL")]
+        public string Url { get; set; }
+
+        /// <summary>
+        /// Gets or sets the height.
+        /// </summary>
+        [JsonPropertyName("height")]
+        public int Height { get; set; }
+
+        /// <summary>
+        /// Gets or sets the width.
+        /// </summary>
+        [JsonPropertyName("width")]
+        public int Width { get; set; }
+
+        /// <summary>
+        /// Gets or sets the md5.
+        /// </summary>
+        [JsonPropertyName("md5")]
+        public string Md5 { get; set; }
+    }
+}

+ 48 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs

@@ -0,0 +1,48 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Map dto.
+    /// </summary>
+    public class MapDto
+    {
+        /// <summary>
+        /// Gets or sets the station id.
+        /// </summary>
+        [JsonPropertyName("stationID")]
+        public string StationId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the channel.
+        /// </summary>
+        [JsonPropertyName("channel")]
+        public string Channel { get; set; }
+
+        /// <summary>
+        /// Gets or sets the logical channel number.
+        /// </summary>
+        [JsonPropertyName("logicalChannelNumber")]
+        public string LogicalChannelNumber { get; set; }
+
+        /// <summary>
+        /// Gets or sets the uhfvhf.
+        /// </summary>
+        [JsonPropertyName("uhfVhf")]
+        public int UhfVhf { get; set; }
+
+        /// <summary>
+        /// Gets or sets the atsc major.
+        /// </summary>
+        [JsonPropertyName("atscMajor")]
+        public int AtscMajor { get; set; }
+
+        /// <summary>
+        /// Gets or sets the atsc minor.
+        /// </summary>
+        [JsonPropertyName("atscMinor")]
+        public int AtscMinor { get; set; }
+    }
+}

+ 30 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs

@@ -0,0 +1,30 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Metadata dto.
+    /// </summary>
+    public class MetadataDto
+    {
+        /// <summary>
+        /// Gets or sets the linup.
+        /// </summary>
+        [JsonPropertyName("lineup")]
+        public string Lineup { get; set; }
+
+        /// <summary>
+        /// Gets or sets the modified timestamp.
+        /// </summary>
+        [JsonPropertyName("modified")]
+        public string Modified { get; set; }
+
+        /// <summary>
+        /// Gets or sets the transport.
+        /// </summary>
+        [JsonPropertyName("transport")]
+        public string Transport { get; set; }
+    }
+}

+ 18 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs

@@ -0,0 +1,18 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Metadata programs dto.
+    /// </summary>
+    public class MetadataProgramsDto
+    {
+        /// <summary>
+        /// Gets or sets the gracenote object.
+        /// </summary>
+        [JsonPropertyName("gracenote")]
+        public GracenoteDto Gracenote { get; set; }
+    }
+}

+ 42 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs

@@ -0,0 +1,42 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Metadata schedule dto.
+    /// </summary>
+    public class MetadataScheduleDto
+    {
+        /// <summary>
+        /// Gets or sets the modified timestamp.
+        /// </summary>
+        [JsonPropertyName("modified")]
+        public string Modified { get; set; }
+
+        /// <summary>
+        /// Gets or sets the md5.
+        /// </summary>
+        [JsonPropertyName("md5")]
+        public string Md5 { get; set; }
+
+        /// <summary>
+        /// Gets or sets the start date.
+        /// </summary>
+        [JsonPropertyName("startDate")]
+        public string StartDate { get; set; }
+
+        /// <summary>
+        /// Gets or sets the end date.
+        /// </summary>
+        [JsonPropertyName("endDate")]
+        public string EndDate { get; set; }
+
+        /// <summary>
+        /// Gets or sets the days count.
+        /// </summary>
+        [JsonPropertyName("days")]
+        public int Days { get; set; }
+    }
+}

+ 31 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs

@@ -0,0 +1,31 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Movie dto.
+    /// </summary>
+    public class MovieDto
+    {
+        /// <summary>
+        /// Gets or sets the year.
+        /// </summary>
+        [JsonPropertyName("year")]
+        public string Year { get; set; }
+
+        /// <summary>
+        /// Gets or sets the duration.
+        /// </summary>
+        [JsonPropertyName("duration")]
+        public int Duration { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of quality rating.
+        /// </summary>
+        [JsonPropertyName("qualityRating")]
+        public List<QualityRatingDto> QualityRating { get; set; }
+    }
+}

+ 24 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs

@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Multipart dto.
+    /// </summary>
+    public class MultipartDto
+    {
+        /// <summary>
+        /// Gets or sets the part number.
+        /// </summary>
+        [JsonPropertyName("partNumber")]
+        public int PartNumber { get; set; }
+
+        /// <summary>
+        /// Gets or sets the total parts.
+        /// </summary>
+        [JsonPropertyName("totalParts")]
+        public int TotalParts { get; set; }
+    }
+}

+ 157 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs

@@ -0,0 +1,157 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Program details dto.
+    /// </summary>
+    public class ProgramDetailsDto
+    {
+        /// <summary>
+        /// Gets or sets the audience.
+        /// </summary>
+        [JsonPropertyName("audience")]
+        public string Audience { get; set; }
+
+        /// <summary>
+        /// Gets or sets the program id.
+        /// </summary>
+        [JsonPropertyName("programID")]
+        public string ProgramId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of titles.
+        /// </summary>
+        [JsonPropertyName("titles")]
+        public List<TitleDto> Titles { get; set; }
+
+        /// <summary>
+        /// Gets or sets the event details object.
+        /// </summary>
+        [JsonPropertyName("eventDetails")]
+        public EventDetailsDto EventDetails { get; set; }
+
+        /// <summary>
+        /// Gets or sets the descriptions.
+        /// </summary>
+        [JsonPropertyName("descriptions")]
+        public DescriptionsProgramDto Descriptions { get; set; }
+
+        /// <summary>
+        /// Gets or sets the original air date.
+        /// </summary>
+        [JsonPropertyName("originalAirDate")]
+        public string OriginalAirDate { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of genres.
+        /// </summary>
+        [JsonPropertyName("genres")]
+        public List<string> Genres { get; set; }
+
+        /// <summary>
+        /// Gets or sets the episode title.
+        /// </summary>
+        [JsonPropertyName("episodeTitle150")]
+        public string EpisodeTitle150 { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of metadata.
+        /// </summary>
+        [JsonPropertyName("metadata")]
+        public List<MetadataProgramsDto> Metadata { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of content raitings.
+        /// </summary>
+        [JsonPropertyName("contentRating")]
+        public List<ContentRatingDto> ContentRating { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of cast.
+        /// </summary>
+        [JsonPropertyName("cast")]
+        public List<CastDto> Cast { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of crew.
+        /// </summary>
+        [JsonPropertyName("crew")]
+        public List<CrewDto> Crew { get; set; }
+
+        /// <summary>
+        /// Gets or sets the entity type.
+        /// </summary>
+        [JsonPropertyName("entityType")]
+        public string EntityType { get; set; }
+
+        /// <summary>
+        /// Gets or sets the show type.
+        /// </summary>
+        [JsonPropertyName("showType")]
+        public string ShowType { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether there is image artwork.
+        /// </summary>
+        [JsonPropertyName("hasImageArtwork")]
+        public bool HasImageArtwork { get; set; }
+
+        /// <summary>
+        /// Gets or sets the primary image.
+        /// </summary>
+        [JsonPropertyName("primaryImage")]
+        public string PrimaryImage { get; set; }
+
+        /// <summary>
+        /// Gets or sets the thumb image.
+        /// </summary>
+        [JsonPropertyName("thumbImage")]
+        public string ThumbImage { get; set; }
+
+        /// <summary>
+        /// Gets or sets the backdrop image.
+        /// </summary>
+        [JsonPropertyName("backdropImage")]
+        public string BackdropImage { get; set; }
+
+        /// <summary>
+        /// Gets or sets the banner image.
+        /// </summary>
+        [JsonPropertyName("bannerImage")]
+        public string BannerImage { get; set; }
+
+        /// <summary>
+        /// Gets or sets the image id.
+        /// </summary>
+        [JsonPropertyName("imageID")]
+        public string ImageId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the md5.
+        /// </summary>
+        [JsonPropertyName("md5")]
+        public string Md5 { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of content advisory.
+        /// </summary>
+        [JsonPropertyName("contentAdvisory")]
+        public List<string> ContentAdvisory { get; set; }
+
+        /// <summary>
+        /// Gets or sets the movie object.
+        /// </summary>
+        [JsonPropertyName("movie")]
+        public MovieDto Movie { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of recommendations.
+        /// </summary>
+        [JsonPropertyName("recommendations")]
+        public List<RecommendationDto> Recommendations { get; set; }
+    }
+}

+ 91 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs

@@ -0,0 +1,91 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Program dto.
+    /// </summary>
+    public class ProgramDto
+    {
+        /// <summary>
+        /// Gets or sets the program id.
+        /// </summary>
+        [JsonPropertyName("programID")]
+        public string ProgramId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the air date time.
+        /// </summary>
+        [JsonPropertyName("airDateTime")]
+        public string AirDateTime { get; set; }
+
+        /// <summary>
+        /// Gets or sets the duration.
+        /// </summary>
+        [JsonPropertyName("duration")]
+        public int Duration { get; set; }
+
+        /// <summary>
+        /// Gets or sets the md5.
+        /// </summary>
+        [JsonPropertyName("md5")]
+        public string Md5 { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of audio properties.
+        /// </summary>
+        [JsonPropertyName("audioProperties")]
+        public List<string> AudioProperties { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of video properties.
+        /// </summary>
+        [JsonPropertyName("videoProperties")]
+        public List<string> VideoProperties { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of ratings.
+        /// </summary>
+        [JsonPropertyName("ratings")]
+        public List<RatingDto> Ratings { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this program is new.
+        /// </summary>
+        [JsonPropertyName("new")]
+        public bool? New { get; set; }
+
+        /// <summary>
+        /// Gets or sets the multipart object.
+        /// </summary>
+        [JsonPropertyName("multipart")]
+        public MultipartDto Multipart { get; set; }
+
+        /// <summary>
+        /// Gets or sets the live tape delay.
+        /// </summary>
+        [JsonPropertyName("liveTapeDelay")]
+        public string LiveTapeDelay { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this is the premiere.
+        /// </summary>
+        [JsonPropertyName("premiere")]
+        public bool Premiere { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this is a repeat.
+        /// </summary>
+        [JsonPropertyName("repeat")]
+        public bool Repeat { get; set; }
+
+        /// <summary>
+        /// Gets or sets the premiere or finale.
+        /// </summary>
+        [JsonPropertyName("isPremiereOrFinale")]
+        public string IsPremiereOrFinale { get; set; }
+    }
+}

+ 42 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs

@@ -0,0 +1,42 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Quality rating dto.
+    /// </summary>
+    public class QualityRatingDto
+    {
+        /// <summary>
+        /// Gets or sets the ratings body.
+        /// </summary>
+        [JsonPropertyName("ratingsBody")]
+        public string RatingsBody { get; set; }
+
+        /// <summary>
+        /// Gets or sets the rating.
+        /// </summary>
+        [JsonPropertyName("rating")]
+        public string Rating { get; set; }
+
+        /// <summary>
+        /// Gets or sets the min rating.
+        /// </summary>
+        [JsonPropertyName("minRating")]
+        public string MinRating { get; set; }
+
+        /// <summary>
+        /// Gets or sets the max rating.
+        /// </summary>
+        [JsonPropertyName("maxRating")]
+        public string MaxRating { get; set; }
+
+        /// <summary>
+        /// Gets or sets the increment.
+        /// </summary>
+        [JsonPropertyName("increment")]
+        public string Increment { get; set; }
+    }
+}

+ 24 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs

@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Rating dto.
+    /// </summary>
+    public class RatingDto
+    {
+        /// <summary>
+        /// Gets or sets the body.
+        /// </summary>
+        [JsonPropertyName("body")]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// Gets or sets the code.
+        /// </summary>
+        [JsonPropertyName("code")]
+        public string Code { get; set; }
+    }
+}

+ 24 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs

@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Recommendation dto.
+    /// </summary>
+    public class RecommendationDto
+    {
+        /// <summary>
+        /// Gets or sets the program id.
+        /// </summary>
+        [JsonPropertyName("programID")]
+        public string ProgramId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the title.
+        /// </summary>
+        [JsonPropertyName("title120")]
+        public string Title120 { get; set; }
+    }
+}

+ 25 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs

@@ -0,0 +1,25 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Request schedule for channel dto.
+    /// </summary>
+    public class RequestScheduleForChannelDto
+    {
+        /// <summary>
+        /// Gets or sets the station id.
+        /// </summary>
+        [JsonPropertyName("stationID")]
+        public string StationId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of dates.
+        /// </summary>
+        [JsonPropertyName("date")]
+        public List<string> Date { get; set; }
+    }
+}

+ 25 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs

@@ -0,0 +1,25 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Show image dto.
+    /// </summary>
+    public class ShowImagesDto
+    {
+        /// <summary>
+        /// Gets or sets the program id.
+        /// </summary>
+        [JsonPropertyName("programID")]
+        public string ProgramId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the list of data.
+        /// </summary>
+        [JsonPropertyName("data")]
+        public List<ImageDataDto> Data { get; set; }
+    }
+}

+ 67 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs

@@ -0,0 +1,67 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+            /// Station dto.
+            /// </summary>
+            public class StationDto
+            {
+                /// <summary>
+                /// Gets or sets the station id.
+                /// </summary>
+                [JsonPropertyName("stationID")]
+                public string StationId { get; set; }
+
+                /// <summary>
+                /// Gets or sets the name.
+                /// </summary>
+                [JsonPropertyName("name")]
+                public string Name { get; set; }
+
+                /// <summary>
+                /// Gets or sets the callsign.
+                /// </summary>
+                [JsonPropertyName("callsign")]
+                public string Callsign { get; set; }
+
+                /// <summary>
+                /// Gets or sets the broadcast language.
+                /// </summary>
+                [JsonPropertyName("broadcastLanguage")]
+                public List<string> BroadcastLanguage { get; set; }
+
+                /// <summary>
+                /// Gets or sets the description language.
+                /// </summary>
+                [JsonPropertyName("descriptionLanguage")]
+                public List<string> DescriptionLanguage { get; set; }
+
+                /// <summary>
+                /// Gets or sets the broadcaster.
+                /// </summary>
+                [JsonPropertyName("broadcaster")]
+                public BroadcasterDto Broadcaster { get; set; }
+
+                /// <summary>
+                /// Gets or sets the affiliate.
+                /// </summary>
+                [JsonPropertyName("affiliate")]
+                public string Affiliate { get; set; }
+
+                /// <summary>
+                /// Gets or sets the logo.
+                /// </summary>
+                [JsonPropertyName("logo")]
+                public LogoDto Logo { get; set; }
+
+                /// <summary>
+                /// Gets or set a value indicating whether it is commercial free.
+                /// </summary>
+                [JsonPropertyName("isCommercialFree")]
+                public bool? IsCommercialFree { get; set; }
+            }
+}

+ 18 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs

@@ -0,0 +1,18 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// Title dto.
+    /// </summary>
+    public class TitleDto
+    {
+        /// <summary>
+        /// Gets or sets the title.
+        /// </summary>
+        [JsonPropertyName("title120")]
+        public string Title120 { get; set; }
+    }
+}

+ 36 - 0
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs

@@ -0,0 +1,36 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+    /// <summary>
+    /// The token dto.
+    /// </summary>
+    public class TokenDto
+    {
+        /// <summary>
+        /// Gets or sets the response code.
+        /// </summary>
+        [JsonPropertyName("code")]
+        public int Code { get; set; }
+
+        /// <summary>
+        /// Gets or sets the response message.
+        /// </summary>
+        [JsonPropertyName("message")]
+        public string Message { get; set; }
+
+        /// <summary>
+        /// Gets or sets the server id.
+        /// </summary>
+        [JsonPropertyName("serverID")]
+        public string ServerId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the token.
+        /// </summary>
+        [JsonPropertyName("token")]
+        public string Token { get; set; }
+    }
+}

+ 28 - 33
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -65,6 +65,8 @@ namespace Emby.Server.Implementations.LiveTv
         private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
         private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
         private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
         private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
 
 
+        private bool _disposed = false;
+
         public LiveTvManager(
         public LiveTvManager(
             IServerConfigurationManager config,
             IServerConfigurationManager config,
             ILogger<LiveTvManager> logger,
             ILogger<LiveTvManager> logger,
@@ -403,7 +405,7 @@ namespace Emby.Server.Implementations.LiveTv
             // Set the total bitrate if not already supplied
             // Set the total bitrate if not already supplied
             mediaSource.InferTotalBitrate();
             mediaSource.InferTotalBitrate();
 
 
-            if (!(service is EmbyTV.EmbyTV))
+            if (service is not EmbyTV.EmbyTV)
             {
             {
                 // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
                 // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
                 // mediaSource.SupportsDirectPlay = false;
                 // mediaSource.SupportsDirectPlay = false;
@@ -520,7 +522,7 @@ namespace Emby.Server.Implementations.LiveTv
             return item;
             return item;
         }
         }
 
 
-        private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
+        private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
         {
         {
             var id = _tvDtoService.GetInternalProgramId(info.Id);
             var id = _tvDtoService.GetInternalProgramId(info.Id);
 
 
@@ -559,8 +561,6 @@ namespace Emby.Server.Implementations.LiveTv
 
 
             item.ParentId = channel.Id;
             item.ParentId = channel.Id;
 
 
-            // item.ChannelType = channelType;
-
             item.Audio = info.Audio;
             item.Audio = info.Audio;
             item.ChannelId = channel.Id;
             item.ChannelId = channel.Id;
             item.CommunityRating ??= info.CommunityRating;
             item.CommunityRating ??= info.CommunityRating;
@@ -772,7 +772,7 @@ namespace Emby.Server.Implementations.LiveTv
                 item.OnMetadataChanged();
                 item.OnMetadataChanged();
             }
             }
 
 
-            return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated);
+            return (item, isNew, isUpdated);
         }
         }
 
 
         public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
         public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
@@ -1187,14 +1187,14 @@ namespace Emby.Server.Implementations.LiveTv
 
 
                     foreach (var program in channelPrograms)
                     foreach (var program in channelPrograms)
                     {
                     {
-                        var programTuple = GetProgram(program, existingPrograms, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken);
-                        var programItem = programTuple.Item1;
+                        var programTuple = GetProgram(program, existingPrograms, currentChannel);
+                        var programItem = programTuple.item;
 
 
-                        if (programTuple.Item2)
+                        if (programTuple.isNew)
                         {
                         {
                             newPrograms.Add(programItem);
                             newPrograms.Add(programItem);
                         }
                         }
-                        else if (programTuple.Item3)
+                        else if (programTuple.isUpdated)
                         {
                         {
                             updatedPrograms.Add(programItem);
                             updatedPrograms.Add(programItem);
                         }
                         }
@@ -1385,10 +1385,10 @@ namespace Emby.Server.Implementations.LiveTv
                 // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
                 // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
 
 
                 // return new QueryResult<BaseItem>
                 // return new QueryResult<BaseItem>
-                //{
+                // {
                 //    Items = items,
                 //    Items = items,
                 //    TotalRecordCount = items.Length
                 //    TotalRecordCount = items.Length
-                //};
+                // };
 
 
                 dtoOptions.Fields = dtoOptions.Fields.Concat(new[] { ItemFields.Tags }).Distinct().ToArray();
                 dtoOptions.Fields = dtoOptions.Fields.Concat(new[] { ItemFields.Tags }).Distinct().ToArray();
             }
             }
@@ -1425,16 +1425,15 @@ namespace Emby.Server.Implementations.LiveTv
             return result;
             return result;
         }
         }
 
 
-        public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> tuples, IReadOnlyList<ItemFields> fields, User user = null)
+        public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
         {
         {
             var programTuples = new List<Tuple<BaseItemDto, string, string>>();
             var programTuples = new List<Tuple<BaseItemDto, string, string>>();
             var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
             var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
             var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
             var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
 
 
-            foreach (var tuple in tuples)
+            foreach (var (item, dto) in programs)
             {
             {
-                var program = (LiveTvProgram)tuple.Item1;
-                var dto = tuple.Item2;
+                var program = (LiveTvProgram)item;
 
 
                 dto.StartDate = program.StartDate;
                 dto.StartDate = program.StartDate;
                 dto.EpisodeTitle = program.EpisodeTitle;
                 dto.EpisodeTitle = program.EpisodeTitle;
@@ -1724,7 +1723,7 @@ namespace Emby.Server.Implementations.LiveTv
 
 
             await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
             await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
 
 
-            if (!(service is EmbyTV.EmbyTV))
+            if (service is not EmbyTV.EmbyTV)
             {
             {
                 TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
                 TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
             }
             }
@@ -1871,11 +1870,11 @@ namespace Emby.Server.Implementations.LiveTv
             return _libraryManager.GetItemById(internalChannelId);
             return _libraryManager.GetItemById(internalChannelId);
         }
         }
 
 
-        public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> tuples, DtoOptions options, User user)
+        public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user)
         {
         {
             var now = DateTime.UtcNow;
             var now = DateTime.UtcNow;
 
 
-            var channelIds = tuples.Select(i => i.Item2.Id).Distinct().ToArray();
+            var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray();
 
 
             var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
             var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
             {
@@ -1896,7 +1895,7 @@ namespace Emby.Server.Implementations.LiveTv
 
 
             var addCurrentProgram = options.AddCurrentProgram;
             var addCurrentProgram = options.AddCurrentProgram;
 
 
-            foreach (var tuple in tuples)
+            foreach (var tuple in items)
             {
             {
                 var dto = tuple.Item1;
                 var dto = tuple.Item1;
                 var channel = tuple.Item2;
                 var channel = tuple.Item2;
@@ -2050,7 +2049,7 @@ namespace Emby.Server.Implementations.LiveTv
 
 
             _logger.LogInformation("New recording scheduled");
             _logger.LogInformation("New recording scheduled");
 
 
-            if (!(service is EmbyTV.EmbyTV))
+            if (service is not EmbyTV.EmbyTV)
             {
             {
                 TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
                 TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
                     new TimerEventInfo(newTimerId)
                     new TimerEventInfo(newTimerId)
@@ -2118,17 +2117,13 @@ namespace Emby.Server.Implementations.LiveTv
             };
             };
         }
         }
 
 
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
+        /// <inheritdoc />
         public void Dispose()
         public void Dispose()
         {
         {
             Dispose(true);
             Dispose(true);
             GC.SuppressFinalize(this);
             GC.SuppressFinalize(this);
         }
         }
 
 
-        private bool _disposed = false;
-
         /// <summary>
         /// <summary>
         /// Releases unmanaged and - optionally - managed resources.
         /// Releases unmanaged and - optionally - managed resources.
         /// </summary>
         /// </summary>
@@ -2324,20 +2319,20 @@ namespace Emby.Server.Implementations.LiveTv
             _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
             _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
         }
         }
 
 
-        public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId)
+        public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
         {
         {
             var config = GetConfiguration();
             var config = GetConfiguration();
 
 
             var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
             var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
-            listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelId, StringComparison.OrdinalIgnoreCase)).ToArray();
+            listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
 
 
-            if (!string.Equals(tunerChannelId, providerChannelId, StringComparison.OrdinalIgnoreCase))
+            if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
             {
             {
                 var list = listingsProviderInfo.ChannelMappings.ToList();
                 var list = listingsProviderInfo.ChannelMappings.ToList();
                 list.Add(new NameValuePair
                 list.Add(new NameValuePair
                 {
                 {
-                    Name = tunerChannelId,
-                    Value = providerChannelId
+                    Name = tunerChannelNumber,
+                    Value = providerChannelNumber
                 });
                 });
                 listingsProviderInfo.ChannelMappings = list.ToArray();
                 listingsProviderInfo.ChannelMappings = list.ToArray();
             }
             }
@@ -2357,10 +2352,10 @@ namespace Emby.Server.Implementations.LiveTv
 
 
             _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
             _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
 
 
-            return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase));
+            return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
         }
         }
 
 
-        public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> epgChannels)
+        public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> providerChannels)
         {
         {
             var result = new TunerChannelMapping
             var result = new TunerChannelMapping
             {
             {
@@ -2373,7 +2368,7 @@ namespace Emby.Server.Implementations.LiveTv
                 result.Name = tunerChannel.Number + " " + result.Name;
                 result.Name = tunerChannel.Number + " " + result.Name;
             }
             }
 
 
-            var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, epgChannels);
+            var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, providerChannels);
 
 
             if (providerChannel != null)
             if (providerChannel != null)
             {
             {

+ 1 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs

@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             return new List<MediaSourceInfo>();
             return new List<MediaSourceInfo>();
         }
         }
 
 
-        protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tuner, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
+        protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
 
 
         public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
         public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
         {
         {

+ 30 - 34
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -95,17 +95,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             public bool IsLegacyTuner { get; set; }
             public bool IsLegacyTuner { get; set; }
         }
         }
 
 
-        protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
+        protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
         {
         {
-            var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false);
+            var lineup = await GetLineup(tuner, cancellationToken).ConfigureAwait(false);
 
 
             return lineup.Select(i => new HdHomerunChannelInfo
             return lineup.Select(i => new HdHomerunChannelInfo
             {
             {
                 Name = i.GuideName,
                 Name = i.GuideName,
                 Number = i.GuideNumber,
                 Number = i.GuideNumber,
-                Id = GetChannelId(info, i),
+                Id = GetChannelId(tuner, i),
                 IsFavorite = i.Favorite,
                 IsFavorite = i.Favorite,
-                TunerHostId = info.Id,
+                TunerHostId = tuner.Id,
                 IsHD = i.HD,
                 IsHD = i.HD,
                 AudioCodec = i.AudioCodec,
                 AudioCodec = i.AudioCodec,
                 VideoCodec = i.VideoCodec,
                 VideoCodec = i.VideoCodec,
@@ -496,57 +496,53 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             return mediaSource;
             return mediaSource;
         }
         }
 
 
-        protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
+        protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken)
         {
         {
             var list = new List<MediaSourceInfo>();
             var list = new List<MediaSourceInfo>();
 
 
-            var channelId = channelInfo.Id;
+            var channelId = channel.Id;
             var hdhrId = GetHdHrIdFromChannelId(channelId);
             var hdhrId = GetHdHrIdFromChannelId(channelId);
 
 
-            var hdHomerunChannelInfo = channelInfo as HdHomerunChannelInfo;
-
-            var isLegacyTuner = hdHomerunChannelInfo != null && hdHomerunChannelInfo.IsLegacyTuner;
-
-            if (isLegacyTuner)
+            if (channel is HdHomerunChannelInfo hdHomerunChannelInfo && hdHomerunChannelInfo.IsLegacyTuner)
             {
             {
-                list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+                list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
             }
             }
             else
             else
             {
             {
-                var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+                var modelInfo = await GetModelInfo(tuner, false, cancellationToken).ConfigureAwait(false);
 
 
                 if (modelInfo != null && modelInfo.SupportsTranscoding)
                 if (modelInfo != null && modelInfo.SupportsTranscoding)
                 {
                 {
-                    if (info.AllowHWTranscoding)
+                    if (tuner.AllowHWTranscoding)
                     {
                     {
-                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
+                        list.Add(GetMediaSource(tuner, hdhrId, channel, "heavy"));
 
 
-                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
-                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
-                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
-                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
-                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
+                        list.Add(GetMediaSource(tuner, hdhrId, channel, "internet540"));
+                        list.Add(GetMediaSource(tuner, hdhrId, channel, "internet480"));
+                        list.Add(GetMediaSource(tuner, hdhrId, channel, "internet360"));
+                        list.Add(GetMediaSource(tuner, hdhrId, channel, "internet240"));
+                        list.Add(GetMediaSource(tuner, hdhrId, channel, "mobile"));
                     }
                     }
 
 
-                    list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+                    list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
                 }
                 }
 
 
                 if (list.Count == 0)
                 if (list.Count == 0)
                 {
                 {
-                    list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+                    list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
                 }
                 }
             }
             }
 
 
             return list;
             return list;
         }
         }
 
 
-        protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
+        protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
         {
         {
-            var tunerCount = info.TunerCount;
+            var tunerCount = tunerHost.TunerCount;
 
 
             if (tunerCount > 0)
             if (tunerCount > 0)
             {
             {
-                var tunerHostId = info.Id;
+                var tunerHostId = tunerHost.Id;
                 var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
                 var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
 
 
                 if (liveStreams.Count() >= tunerCount)
                 if (liveStreams.Count() >= tunerCount)
@@ -557,26 +553,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             var profile = streamId.Split('_')[0];
             var profile = streamId.Split('_')[0];
 
 
-            Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelInfo.Id, streamId, profile);
+            Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channel.Id, streamId, profile);
 
 
-            var hdhrId = GetHdHrIdFromChannelId(channelInfo.Id);
+            var hdhrId = GetHdHrIdFromChannelId(channel.Id);
 
 
-            var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
+            var hdhomerunChannel = channel as HdHomerunChannelInfo;
 
 
-            var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+            var modelInfo = await GetModelInfo(tunerHost, false, cancellationToken).ConfigureAwait(false);
 
 
             if (!modelInfo.SupportsTranscoding)
             if (!modelInfo.SupportsTranscoding)
             {
             {
                 profile = "native";
                 profile = "native";
             }
             }
 
 
-            var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
+            var mediaSource = GetMediaSource(tunerHost, hdhrId, channel, profile);
 
 
             if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
             if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
             {
             {
                 return new HdHomerunUdpStream(
                 return new HdHomerunUdpStream(
                     mediaSource,
                     mediaSource,
-                    info,
+                    tunerHost,
                     streamId,
                     streamId,
                     new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
                     new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
                     modelInfo.TunerCount,
                     modelInfo.TunerCount,
@@ -592,7 +588,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
             {
                 mediaSource.Protocol = MediaProtocol.Http;
                 mediaSource.Protocol = MediaProtocol.Http;
 
 
-                var httpUrl = channelInfo.Path;
+                var httpUrl = channel.Path;
 
 
                 // If raw was used, the tuner doesn't support params
                 // If raw was used, the tuner doesn't support params
                 if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
                 if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
@@ -604,7 +600,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
                 return new SharedHttpStream(
                 return new SharedHttpStream(
                     mediaSource,
                     mediaSource,
-                    info,
+                    tunerHost,
                     streamId,
                     streamId,
                     FileSystem,
                     FileSystem,
                     _httpClientFactory,
                     _httpClientFactory,
@@ -616,7 +612,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             return new HdHomerunUdpStream(
             return new HdHomerunUdpStream(
                 mediaSource,
                 mediaSource,
-                info,
+                tunerHost,
                 streamId,
                 streamId,
                 new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
                 new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
                 modelInfo.TunerCount,
                 modelInfo.TunerCount,

+ 1 - 0
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs

@@ -27,6 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
     {
     {
         private string _channel;
         private string _channel;
         private string _program;
         private string _program;
+
         public LegacyHdHomerunChannelCommands(string url)
         public LegacyHdHomerunChannelCommands(string url)
         {
         {
             // parse url for channel and program
             // parse url for channel and program

+ 11 - 11
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -71,12 +71,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
             return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
         }
         }
 
 
-        protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
+        protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
         {
         {
-            var channelIdPrefix = GetFullChannelIdPrefix(info);
+            var channelIdPrefix = GetFullChannelIdPrefix(tuner);
 
 
             return await new M3uParser(Logger, _httpClientFactory)
             return await new M3uParser(Logger, _httpClientFactory)
-                .Parse(info, channelIdPrefix, cancellationToken)
+                .Parse(tuner, channelIdPrefix, cancellationToken)
                 .ConfigureAwait(false);
                 .ConfigureAwait(false);
         }
         }
 
 
@@ -96,13 +96,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             return Task.FromResult(list);
             return Task.FromResult(list);
         }
         }
 
 
-        protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
+        protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
         {
         {
-            var tunerCount = info.TunerCount;
+            var tunerCount = tunerHost.TunerCount;
 
 
             if (tunerCount > 0)
             if (tunerCount > 0)
             {
             {
-                var tunerHostId = info.Id;
+                var tunerHostId = tunerHost.Id;
                 var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
                 var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
 
 
                 if (liveStreams.Count() >= tunerCount)
                 if (liveStreams.Count() >= tunerCount)
@@ -111,7 +111,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
                 }
                 }
             }
             }
 
 
-            var sources = await GetChannelStreamMediaSources(info, channelInfo, cancellationToken).ConfigureAwait(false);
+            var sources = await GetChannelStreamMediaSources(tunerHost, channel, cancellationToken).ConfigureAwait(false);
 
 
             var mediaSource = sources[0];
             var mediaSource = sources[0];
 
 
@@ -121,11 +121,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
                 if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
                 if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
                 {
                 {
-                    return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
+                    return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
                 }
                 }
             }
             }
 
 
-            return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper);
+            return new LiveStream(mediaSource, tunerHost, FileSystem, Logger, Config, _streamHelper);
         }
         }
 
 
         public async Task Validate(TunerHostInfo info)
         public async Task Validate(TunerHostInfo info)
@@ -135,9 +135,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             }
             }
         }
         }
 
 
-        protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
+        protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken)
         {
         {
-            return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channelInfo) });
+            return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(tuner, channel) });
         }
         }
 
 
         protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel)
         protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel)

+ 2 - 2
Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs

@@ -295,11 +295,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
                 }
                 }
             }
             }
 
 
-            attributes.TryGetValue("tvg-name", out string name);
+            string name = nameInExtInf;
 
 
             if (string.IsNullOrWhiteSpace(name))
             if (string.IsNullOrWhiteSpace(name))
             {
             {
-                name = nameInExtInf;
+                attributes.TryGetValue("tvg-name", out name);
             }
             }
 
 
             if (string.IsNullOrWhiteSpace(name))
             if (string.IsNullOrWhiteSpace(name))

+ 2 - 2
Emby.Server.Implementations/Localization/Core/bg-BG.json

@@ -25,7 +25,7 @@
     "HeaderLiveTV": "Телевизия на живо",
     "HeaderLiveTV": "Телевизия на живо",
     "HeaderNextUp": "Следва",
     "HeaderNextUp": "Следва",
     "HeaderRecordingGroups": "Запис групи",
     "HeaderRecordingGroups": "Запис групи",
-    "HomeVideos": "Домашни клипове",
+    "HomeVideos": "Домашни Клипове",
     "Inherit": "Наследяване",
     "Inherit": "Наследяване",
     "ItemAddedWithName": "{0} е добавено към библиотеката",
     "ItemAddedWithName": "{0} е добавено към библиотеката",
     "ItemRemovedWithName": "{0} е премахнато от библиотеката",
     "ItemRemovedWithName": "{0} е премахнато от библиотеката",
@@ -39,7 +39,7 @@
     "MixedContent": "Смесено съдържание",
     "MixedContent": "Смесено съдържание",
     "Movies": "Филми",
     "Movies": "Филми",
     "Music": "Музика",
     "Music": "Музика",
-    "MusicVideos": "Музикални видеа",
+    "MusicVideos": "Музикални Видеа",
     "NameInstallFailed": "{0} не можа да се инсталира",
     "NameInstallFailed": "{0} не можа да се инсталира",
     "NameSeasonNumber": "Сезон {0}",
     "NameSeasonNumber": "Сезон {0}",
     "NameSeasonUnknown": "Неразпознат сезон",
     "NameSeasonUnknown": "Неразпознат сезон",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/tr.json

@@ -25,7 +25,7 @@
     "HeaderLiveTV": "Canlı TV",
     "HeaderLiveTV": "Canlı TV",
     "HeaderNextUp": "Gelecek Hafta",
     "HeaderNextUp": "Gelecek Hafta",
     "HeaderRecordingGroups": "Kayıt Grupları",
     "HeaderRecordingGroups": "Kayıt Grupları",
-    "HomeVideos": "Ev videoları",
+    "HomeVideos": "Ana sayfa videoları",
     "Inherit": "Devral",
     "Inherit": "Devral",
     "ItemAddedWithName": "{0} kütüphaneye eklendi",
     "ItemAddedWithName": "{0} kütüphaneye eklendi",
     "ItemRemovedWithName": "{0} kütüphaneden silindi",
     "ItemRemovedWithName": "{0} kütüphaneden silindi",

+ 6 - 14
Emby.Server.Implementations/Net/SocketFactory.cs

@@ -11,6 +11,7 @@ namespace Emby.Server.Implementations.Net
 {
 {
     public class SocketFactory : ISocketFactory
     public class SocketFactory : ISocketFactory
     {
     {
+        /// <inheritdoc />
         public ISocket CreateUdpBroadcastSocket(int localPort)
         public ISocket CreateUdpBroadcastSocket(int localPort)
         {
         {
             if (localPort < 0)
             if (localPort < 0)
@@ -35,11 +36,8 @@ namespace Emby.Server.Implementations.Net
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
-        /// </summary>
-        /// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
-        public ISocket CreateSsdpUdpSocket(IPAddress localIpAddress, int localPort)
+        /// <inheritdoc />
+        public ISocket CreateSsdpUdpSocket(IPAddress localIp, int localPort)
         {
         {
             if (localPort < 0)
             if (localPort < 0)
             {
             {
@@ -53,8 +51,8 @@ namespace Emby.Server.Implementations.Net
                 retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                 retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                 retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
                 retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
 
 
-                retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress));
-                return new UdpSocket(retVal, localPort, localIpAddress);
+                retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp));
+                return new UdpSocket(retVal, localPort, localIp);
             }
             }
             catch
             catch
             {
             {
@@ -64,13 +62,7 @@ namespace Emby.Server.Implementations.Net
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Creates a new UDP acceptSocket that is a member of the specified multicast IP address, and binds it to the specified local port.
-        /// </summary>
-        /// <param name="ipAddress">The multicast IP address to make the acceptSocket a member of.</param>
-        /// <param name="multicastTimeToLive">The multicast time to live value for the acceptSocket.</param>
-        /// <param name="localPort">The number of the local port to bind to.</param>
-        /// <returns></returns>
+        /// <inheritdoc />
         public ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
         public ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
         {
         {
             if (ipAddress == null)
             if (ipAddress == null)

+ 2 - 2
Emby.Server.Implementations/Net/UdpSocket.cs

@@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Net
             return taskCompletion.Task;
             return taskCompletion.Task;
         }
         }
 
 
-        public Task SendToAsync(byte[] buffer, int offset, int size, IPEndPoint endPoint, CancellationToken cancellationToken)
+        public Task SendToAsync(byte[] buffer, int offset, int bytes, IPEndPoint endPoint, CancellationToken cancellationToken)
         {
         {
             ThrowIfDisposed();
             ThrowIfDisposed();
 
 
@@ -214,7 +214,7 @@ namespace Emby.Server.Implementations.Net
                 }
                 }
             };
             };
 
 
-            var result = BeginSendTo(buffer, offset, size, endPoint, new AsyncCallback(callback), null);
+            var result = BeginSendTo(buffer, offset, bytes, endPoint, new AsyncCallback(callback), null);
 
 
             if (result.CompletedSynchronously)
             if (result.CompletedSynchronously)
             {
             {

+ 2 - 2
Emby.Server.Implementations/Playlists/PlaylistManager.cs

@@ -260,7 +260,7 @@ namespace Emby.Server.Implementations.Playlists
 
 
         public async Task RemoveFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds)
         public async Task RemoveFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds)
         {
         {
-            if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
+            if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
             {
             {
                 throw new ArgumentException("No Playlist exists with the supplied Id");
                 throw new ArgumentException("No Playlist exists with the supplied Id");
             }
             }
@@ -293,7 +293,7 @@ namespace Emby.Server.Implementations.Playlists
 
 
         public async Task MoveItemAsync(string playlistId, string entryId, int newIndex)
         public async Task MoveItemAsync(string playlistId, string entryId, int newIndex)
         {
         {
-            if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
+            if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
             {
             {
                 throw new ArgumentException("No Playlist exists with the supplied Id");
                 throw new ArgumentException("No Playlist exists with the supplied Id");
             }
             }

+ 1 - 0
Emby.Server.Implementations/Properties/AssemblyInfo.cs

@@ -16,6 +16,7 @@ using System.Runtime.InteropServices;
 [assembly: AssemblyCulture("")]
 [assembly: AssemblyCulture("")]
 [assembly: NeutralResourcesLanguage("en")]
 [assembly: NeutralResourcesLanguage("en")]
 [assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")]
 [assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")]
+[assembly: InternalsVisibleTo("Emby.Server.Implementations.Fuzz")]
 
 
 // Setting ComVisible to false makes the types in this assembly not visible
 // Setting ComVisible to false makes the types in this assembly not visible
 // to COM components.  If you need to access a type in this assembly from
 // to COM components.  If you need to access a type in this assembly from

+ 79 - 34
Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs

@@ -3,12 +3,13 @@ using System.Collections.Concurrent;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Security.Cryptography;
 using System.Security.Cryptography;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.QuickConnect;
 using MediaBrowser.Controller.QuickConnect;
-using MediaBrowser.Controller.Security;
+using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.QuickConnect;
 using MediaBrowser.Model.QuickConnect;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
@@ -19,11 +20,6 @@ namespace Emby.Server.Implementations.QuickConnect
     /// </summary>
     /// </summary>
     public class QuickConnectManager : IQuickConnect, IDisposable
     public class QuickConnectManager : IQuickConnect, IDisposable
     {
     {
-        /// <summary>
-        /// The name of internal access tokens.
-        /// </summary>
-        private const string TokenName = "QuickConnect";
-
         /// <summary>
         /// <summary>
         /// The length of user facing codes.
         /// The length of user facing codes.
         /// </summary>
         /// </summary>
@@ -34,13 +30,13 @@ namespace Emby.Server.Implementations.QuickConnect
         /// </summary>
         /// </summary>
         private const int Timeout = 10;
         private const int Timeout = 10;
 
 
-        private readonly RNGCryptoServiceProvider _rng = new();
-        private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new();
+        private readonly RNGCryptoServiceProvider _rng = new ();
+        private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new ();
+        private readonly ConcurrentDictionary<string, (DateTime Timestamp, AuthenticationResult AuthenticationResult)> _authorizedSecrets = new ();
 
 
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly ILogger<QuickConnectManager> _logger;
         private readonly ILogger<QuickConnectManager> _logger;
-        private readonly IServerApplicationHost _appHost;
-        private readonly IAuthenticationRepository _authenticationRepository;
+        private readonly ISessionManager _sessionManager;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="QuickConnectManager"/> class.
         /// Initializes a new instance of the <see cref="QuickConnectManager"/> class.
@@ -48,18 +44,15 @@ namespace Emby.Server.Implementations.QuickConnect
         /// </summary>
         /// </summary>
         /// <param name="config">Configuration.</param>
         /// <param name="config">Configuration.</param>
         /// <param name="logger">Logger.</param>
         /// <param name="logger">Logger.</param>
-        /// <param name="appHost">Application host.</param>
-        /// <param name="authenticationRepository">Authentication repository.</param>
+        /// <param name="sessionManager">Session Manager.</param>
         public QuickConnectManager(
         public QuickConnectManager(
             IServerConfigurationManager config,
             IServerConfigurationManager config,
             ILogger<QuickConnectManager> logger,
             ILogger<QuickConnectManager> logger,
-            IServerApplicationHost appHost,
-            IAuthenticationRepository authenticationRepository)
+            ISessionManager sessionManager)
         {
         {
             _config = config;
             _config = config;
             _logger = logger;
             _logger = logger;
-            _appHost = appHost;
-            _authenticationRepository = authenticationRepository;
+            _sessionManager = sessionManager;
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -77,14 +70,41 @@ namespace Emby.Server.Implementations.QuickConnect
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public QuickConnectResult TryConnect()
+        public QuickConnectResult TryConnect(AuthorizationInfo authorizationInfo)
         {
         {
+            if (string.IsNullOrEmpty(authorizationInfo.DeviceId))
+            {
+                throw new ArgumentException(nameof(authorizationInfo.DeviceId) + " is required");
+            }
+
+            if (string.IsNullOrEmpty(authorizationInfo.Device))
+            {
+                throw new ArgumentException(nameof(authorizationInfo.Device) + " is required");
+            }
+
+            if (string.IsNullOrEmpty(authorizationInfo.Client))
+            {
+                throw new ArgumentException(nameof(authorizationInfo.Client) + " is required");
+            }
+
+            if (string.IsNullOrEmpty(authorizationInfo.Version))
+            {
+                throw new ArgumentException(nameof(authorizationInfo.Version) + "is required");
+            }
+
             AssertActive();
             AssertActive();
             ExpireRequests();
             ExpireRequests();
 
 
             var secret = GenerateSecureRandom();
             var secret = GenerateSecureRandom();
             var code = GenerateCode();
             var code = GenerateCode();
-            var result = new QuickConnectResult(secret, code, DateTime.UtcNow);
+            var result = new QuickConnectResult(
+                secret,
+                code,
+                DateTime.UtcNow,
+                authorizationInfo.DeviceId,
+                authorizationInfo.Device,
+                authorizationInfo.Client,
+                authorizationInfo.Version);
 
 
             _currentRequests[code] = result;
             _currentRequests[code] = result;
             return result;
             return result;
@@ -129,7 +149,7 @@ namespace Emby.Server.Implementations.QuickConnect
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public bool AuthorizeRequest(Guid userId, string code)
+        public async Task<bool> AuthorizeRequest(Guid userId, string code)
         {
         {
             AssertActive();
             AssertActive();
             ExpireRequests();
             ExpireRequests();
@@ -144,28 +164,41 @@ namespace Emby.Server.Implementations.QuickConnect
                 throw new InvalidOperationException("Request is already authorized");
                 throw new InvalidOperationException("Request is already authorized");
             }
             }
 
 
-            var token = Guid.NewGuid();
-            result.Authentication = token;
-
             // Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated.
             // Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated.
-            result.DateAdded = DateTime.Now.Add(TimeSpan.FromMinutes(1));
+            result.DateAdded = DateTime.UtcNow.Add(TimeSpan.FromMinutes(1));
 
 
-            _authenticationRepository.Create(new AuthenticationInfo
+            var authenticationResult = await _sessionManager.AuthenticateDirect(new AuthenticationRequest
             {
             {
-                AppName = TokenName,
-                AccessToken = token.ToString("N", CultureInfo.InvariantCulture),
-                DateCreated = DateTime.UtcNow,
-                DeviceId = _appHost.SystemId,
-                DeviceName = _appHost.FriendlyName,
-                AppVersion = _appHost.ApplicationVersionString,
-                UserId = userId
-            });
+                UserId = userId,
+                DeviceId = result.DeviceId,
+                DeviceName = result.DeviceName,
+                App = result.AppName,
+                AppVersion = result.AppVersion
+            }).ConfigureAwait(false);
+
+            _authorizedSecrets[result.Secret] = (DateTime.UtcNow, authenticationResult);
+            result.Authenticated = true;
+            _currentRequests[code] = result;
 
 
-            _logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId);
+            _logger.LogDebug("Authorizing device with code {Code} to login as user {UserId}", code, userId);
 
 
             return true;
             return true;
         }
         }
 
 
+        /// <inheritdoc/>
+        public AuthenticationResult GetAuthorizedRequest(string secret)
+        {
+            AssertActive();
+            ExpireRequests();
+
+            if (!_authorizedSecrets.TryGetValue(secret, out var result))
+            {
+                throw new ResourceNotFoundException("Unable to find request");
+            }
+
+            return result.AuthenticationResult;
+        }
+
         /// <summary>
         /// <summary>
         /// Dispose.
         /// Dispose.
         /// </summary>
         /// </summary>
@@ -218,6 +251,18 @@ namespace Emby.Server.Implementations.QuickConnect
                     }
                     }
                 }
                 }
             }
             }
+
+            foreach (var (secret, (timestamp, _)) in _authorizedSecrets)
+            {
+                if (expireAll || timestamp < minTime)
+                {
+                    _logger.LogDebug("Removing expired secret {Secret}", secret);
+                    if (!_authorizedSecrets.TryRemove(secret, out _))
+                    {
+                        _logger.LogWarning("Secret {Secret} already expired", secret);
+                    }
+                }
+            }
         }
         }
     }
     }
 }
 }

+ 1 - 2
Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -24,7 +24,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
     /// </summary>
     /// </summary>
     public class ScheduledTaskWorker : IScheduledTaskWorker
     public class ScheduledTaskWorker : IScheduledTaskWorker
     {
     {
-
         /// <summary>
         /// <summary>
         /// Gets or sets the application paths.
         /// Gets or sets the application paths.
         /// </summary>
         /// </summary>
@@ -267,7 +266,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the triggers that define when the task will run.
+        /// Gets or sets the triggers that define when the task will run.
         /// </summary>
         /// </summary>
         /// <value>The triggers.</value>
         /// <value>The triggers.</value>
         /// <exception cref="ArgumentNullException"><c>value</c> is <c>null</c>.</exception>
         /// <exception cref="ArgumentNullException"><c>value</c> is <c>null</c>.</exception>

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs

@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
         public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
         {
             var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
             var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
-            if (!retentionDays.HasValue || retentionDays <= 0)
+            if (!retentionDays.HasValue || retentionDays < 0)
             {
             {
                 throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
                 throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
             }
             }

+ 4 - 0
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs

@@ -29,6 +29,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
         /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
         /// </summary>
         /// </summary>
+        /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
+        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
         public DeleteCacheFileTask(
         public DeleteCacheFileTask(
             IApplicationPaths appPaths,
             IApplicationPaths appPaths,
             ILogger<DeleteCacheFileTask> logger,
             ILogger<DeleteCacheFileTask> logger,

+ 0 - 408
Emby.Server.Implementations/Security/AuthenticationRepository.cs

@@ -1,408 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using Emby.Server.Implementations.Data;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Devices;
-using MediaBrowser.Model.Querying;
-using Microsoft.Extensions.Logging;
-using SQLitePCL.pretty;
-
-namespace Emby.Server.Implementations.Security
-{
-    public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
-    {
-        public AuthenticationRepository(ILogger<AuthenticationRepository> logger, IServerConfigurationManager config)
-            : base(logger)
-        {
-            DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db");
-        }
-
-        public void Initialize()
-        {
-            string[] queries =
-            {
-                "create table if not exists Tokens (Id INTEGER PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, UserName TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateLastActivity DATETIME NOT NULL)",
-                "create table if not exists Devices (Id TEXT NOT NULL PRIMARY KEY, CustomName TEXT, Capabilities TEXT)",
-                "drop index if exists idx_AccessTokens",
-                "drop index if exists Tokens1",
-                "drop index if exists Tokens2",
-
-                "create index if not exists Tokens3 on Tokens (AccessToken, DateLastActivity)",
-                "create index if not exists Tokens4 on Tokens (Id, DateLastActivity)",
-                "create index if not exists Devices1 on Devices (Id)"
-            };
-
-            using (var connection = GetConnection())
-            {
-                var tableNewlyCreated = !TableExists(connection, "Tokens");
-
-                connection.RunQueries(queries);
-
-                TryMigrate(connection, tableNewlyCreated);
-            }
-        }
-
-        private void TryMigrate(ManagedConnection connection, bool tableNewlyCreated)
-        {
-            try
-            {
-                if (tableNewlyCreated && TableExists(connection, "AccessTokens"))
-                {
-                    connection.RunInTransaction(
-                    db =>
-                    {
-                        var existingColumnNames = GetColumnNames(db, "AccessTokens");
-
-                        AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames);
-                        AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames);
-                        AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames);
-                    }, TransactionMode);
-
-                    connection.RunQueries(new[]
-                    {
-                        "update accesstokens set DateLastActivity=DateCreated where DateLastActivity is null",
-                        "update accesstokens set DeviceName='Unknown' where DeviceName is null",
-                        "update accesstokens set AppName='Unknown' where AppName is null",
-                        "update accesstokens set AppVersion='1' where AppVersion is null",
-                        "INSERT INTO Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) SELECT AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity FROM AccessTokens where deviceid not null and devicename not null and appname not null and isactive=1"
-                    });
-                }
-            }
-            catch (Exception ex)
-            {
-                Logger.LogError(ex, "Error migrating authentication database");
-            }
-        }
-
-        public void Create(AuthenticationInfo info)
-        {
-            if (info == null)
-            {
-                throw new ArgumentNullException(nameof(info));
-            }
-
-            using (var connection = GetConnection())
-            {
-                connection.RunInTransaction(
-                db =>
-                {
-                    using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)"))
-                    {
-                        statement.TryBind("@AccessToken", info.AccessToken);
-
-                        statement.TryBind("@DeviceId", info.DeviceId);
-                        statement.TryBind("@AppName", info.AppName);
-                        statement.TryBind("@AppVersion", info.AppVersion);
-                        statement.TryBind("@DeviceName", info.DeviceName);
-                        statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture));
-                        statement.TryBind("@UserName", info.UserName);
-                        statement.TryBind("@IsActive", true);
-                        statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
-                        statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
-
-                        statement.MoveNext();
-                    }
-                }, TransactionMode);
-            }
-        }
-
-        public void Update(AuthenticationInfo info)
-        {
-            if (info == null)
-            {
-                throw new ArgumentNullException(nameof(info));
-            }
-
-            using (var connection = GetConnection())
-            {
-                connection.RunInTransaction(
-                db =>
-                {
-                    using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id"))
-                    {
-                        statement.TryBind("@Id", info.Id);
-
-                        statement.TryBind("@AccessToken", info.AccessToken);
-
-                        statement.TryBind("@DeviceId", info.DeviceId);
-                        statement.TryBind("@AppName", info.AppName);
-                        statement.TryBind("@AppVersion", info.AppVersion);
-                        statement.TryBind("@DeviceName", info.DeviceName);
-                        statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture));
-                        statement.TryBind("@UserName", info.UserName);
-                        statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
-                        statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
-
-                        statement.MoveNext();
-                    }
-                }, TransactionMode);
-            }
-        }
-
-        public void Delete(AuthenticationInfo info)
-        {
-            if (info == null)
-            {
-                throw new ArgumentNullException(nameof(info));
-            }
-
-            using (var connection = GetConnection())
-            {
-                connection.RunInTransaction(
-                db =>
-                {
-                    using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id"))
-                    {
-                        statement.TryBind("@Id", info.Id);
-
-                        statement.MoveNext();
-                    }
-                }, TransactionMode);
-            }
-        }
-
-        private const string BaseSelectText = "select Tokens.Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, DateCreated, DateLastActivity, Devices.CustomName from Tokens left join Devices on Tokens.DeviceId=Devices.Id";
-
-        private static void BindAuthenticationQueryParams(AuthenticationInfoQuery query, IStatement statement)
-        {
-            if (!string.IsNullOrEmpty(query.AccessToken))
-            {
-                statement.TryBind("@AccessToken", query.AccessToken);
-            }
-
-            if (!query.UserId.Equals(Guid.Empty))
-            {
-                statement.TryBind("@UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture));
-            }
-
-            if (!string.IsNullOrEmpty(query.DeviceId))
-            {
-                statement.TryBind("@DeviceId", query.DeviceId);
-            }
-        }
-
-        public QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query)
-        {
-            if (query == null)
-            {
-                throw new ArgumentNullException(nameof(query));
-            }
-
-            var commandText = BaseSelectText;
-
-            var whereClauses = new List<string>();
-
-            if (!string.IsNullOrEmpty(query.AccessToken))
-            {
-                whereClauses.Add("AccessToken=@AccessToken");
-            }
-
-            if (!string.IsNullOrEmpty(query.DeviceId))
-            {
-                whereClauses.Add("DeviceId=@DeviceId");
-            }
-
-            if (!query.UserId.Equals(Guid.Empty))
-            {
-                whereClauses.Add("UserId=@UserId");
-            }
-
-            if (query.HasUser.HasValue)
-            {
-                if (query.HasUser.Value)
-                {
-                    whereClauses.Add("UserId not null");
-                }
-                else
-                {
-                    whereClauses.Add("UserId is null");
-                }
-            }
-
-            var whereTextWithoutPaging = whereClauses.Count == 0 ?
-              string.Empty :
-              " where " + string.Join(" AND ", whereClauses.ToArray());
-
-            commandText += whereTextWithoutPaging;
-
-            commandText += " ORDER BY DateLastActivity desc";
-
-            if (query.Limit.HasValue || query.StartIndex.HasValue)
-            {
-                var offset = query.StartIndex ?? 0;
-
-                if (query.Limit.HasValue || offset > 0)
-                {
-                    commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
-                }
-
-                if (offset > 0)
-                {
-                    commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
-                }
-            }
-
-            var statementTexts = new[]
-            {
-                commandText,
-                "select count (Id) from Tokens" + whereTextWithoutPaging
-            };
-
-            var list = new List<AuthenticationInfo>();
-            var result = new QueryResult<AuthenticationInfo>();
-            using (var connection = GetConnection(true))
-            {
-                connection.RunInTransaction(
-                    db =>
-                    {
-                        var statements = PrepareAll(db, statementTexts);
-
-                        using (var statement = statements[0])
-                        {
-                            BindAuthenticationQueryParams(query, statement);
-
-                            foreach (var row in statement.ExecuteQuery())
-                            {
-                                list.Add(Get(row));
-                            }
-
-                            using (var totalCountStatement = statements[1])
-                            {
-                                BindAuthenticationQueryParams(query, totalCountStatement);
-
-                                result.TotalRecordCount = totalCountStatement.ExecuteQuery()
-                                    .SelectScalarInt()
-                                    .First();
-                            }
-                        }
-                    },
-                    ReadTransactionMode);
-            }
-
-            result.Items = list;
-            return result;
-        }
-
-        private static AuthenticationInfo Get(IReadOnlyList<ResultSetValue> reader)
-        {
-            var info = new AuthenticationInfo
-            {
-                Id = reader[0].ToInt64(),
-                AccessToken = reader[1].ToString()
-            };
-
-            if (reader.TryGetString(2, out var deviceId))
-            {
-                info.DeviceId = deviceId;
-            }
-
-            if (reader.TryGetString(3, out var appName))
-            {
-                info.AppName = appName;
-            }
-
-            if (reader.TryGetString(4, out var appVersion))
-            {
-                info.AppVersion = appVersion;
-            }
-
-            if (reader.TryGetString(6, out var userId))
-            {
-                info.UserId = new Guid(userId);
-            }
-
-            if (reader.TryGetString(7, out var userName))
-            {
-                info.UserName = userName;
-            }
-
-            info.DateCreated = reader[8].ReadDateTime();
-
-            if (reader.TryReadDateTime(9, out var dateLastActivity))
-            {
-                info.DateLastActivity = dateLastActivity;
-            }
-            else
-            {
-                info.DateLastActivity = info.DateCreated;
-            }
-
-            if (reader.TryGetString(10, out var customName))
-            {
-                info.DeviceName = customName;
-            }
-            else if (reader.TryGetString(5, out var deviceName))
-            {
-                info.DeviceName = deviceName;
-            }
-
-            return info;
-        }
-
-        public DeviceOptions GetDeviceOptions(string deviceId)
-        {
-            using (var connection = GetConnection(true))
-            {
-                return connection.RunInTransaction(
-                db =>
-                {
-                    using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId"))
-                    {
-                        statement.TryBind("@DeviceId", deviceId);
-
-                        var result = new DeviceOptions();
-
-                        foreach (var row in statement.ExecuteQuery())
-                        {
-                            if (row.TryGetString(0, out var customName))
-                            {
-                                result.CustomName = customName;
-                            }
-                        }
-
-                        return result;
-                    }
-                }, ReadTransactionMode);
-            }
-        }
-
-        public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
-        {
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            using (var connection = GetConnection())
-            {
-                connection.RunInTransaction(
-                db =>
-                {
-                    using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))"))
-                    {
-                        statement.TryBind("@Id", deviceId);
-
-                        if (string.IsNullOrWhiteSpace(options.CustomName))
-                        {
-                            statement.TryBindNull("@CustomName");
-                        }
-                        else
-                        {
-                            statement.TryBind("@CustomName", options.CustomName);
-                        }
-
-                        statement.MoveNext();
-                    }
-                }, TransactionMode);
-            }
-        }
-    }
-}

+ 68 - 120
Emby.Server.Implementations/Session/SessionManager.cs

@@ -10,8 +10,10 @@ using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Entities;
+using Jellyfin.Data.Entities.Security;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Events;
 using Jellyfin.Data.Events;
+using Jellyfin.Data.Queries;
 using Jellyfin.Extensions;
 using Jellyfin.Extensions;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
@@ -25,9 +27,7 @@ using MediaBrowser.Controller.Events;
 using MediaBrowser.Controller.Events.Session;
 using MediaBrowser.Controller.Events.Session;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Library;
@@ -55,7 +55,6 @@ namespace Emby.Server.Implementations.Session
         private readonly IImageProcessor _imageProcessor;
         private readonly IImageProcessor _imageProcessor;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
-        private readonly IAuthenticationRepository _authRepo;
         private readonly IDeviceManager _deviceManager;
         private readonly IDeviceManager _deviceManager;
 
 
         /// <summary>
         /// <summary>
@@ -78,7 +77,6 @@ namespace Emby.Server.Implementations.Session
             IDtoService dtoService,
             IDtoService dtoService,
             IImageProcessor imageProcessor,
             IImageProcessor imageProcessor,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
-            IAuthenticationRepository authRepo,
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
             IMediaSourceManager mediaSourceManager)
             IMediaSourceManager mediaSourceManager)
         {
         {
@@ -91,7 +89,6 @@ namespace Emby.Server.Implementations.Session
             _dtoService = dtoService;
             _dtoService = dtoService;
             _imageProcessor = imageProcessor;
             _imageProcessor = imageProcessor;
             _appHost = appHost;
             _appHost = appHost;
-            _authRepo = authRepo;
             _deviceManager = deviceManager;
             _deviceManager = deviceManager;
             _mediaSourceManager = mediaSourceManager;
             _mediaSourceManager = mediaSourceManager;
 
 
@@ -238,12 +235,12 @@ namespace Emby.Server.Implementations.Session
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void UpdateDeviceName(string sessionId, string deviceName)
+        public void UpdateDeviceName(string sessionId, string reportedDeviceName)
         {
         {
             var session = GetSession(sessionId);
             var session = GetSession(sessionId);
             if (session != null)
             if (session != null)
             {
             {
-                session.DeviceName = deviceName;
+                session.DeviceName = reportedDeviceName;
             }
             }
         }
         }
 
 
@@ -257,7 +254,7 @@ namespace Emby.Server.Implementations.Session
         /// <param name="remoteEndPoint">The remote end point.</param>
         /// <param name="remoteEndPoint">The remote end point.</param>
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <returns>SessionInfo.</returns>
         /// <returns>SessionInfo.</returns>
-        public SessionInfo LogSessionActivity(
+        public async Task<SessionInfo> LogSessionActivity(
             string appName,
             string appName,
             string appVersion,
             string appVersion,
             string deviceId,
             string deviceId,
@@ -283,7 +280,7 @@ namespace Emby.Server.Implementations.Session
             }
             }
 
 
             var activityDate = DateTime.UtcNow;
             var activityDate = DateTime.UtcNow;
-            var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user);
+            var session = await GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false);
             var lastActivityDate = session.LastActivityDate;
             var lastActivityDate = session.LastActivityDate;
             session.LastActivityDate = activityDate;
             session.LastActivityDate = activityDate;
 
 
@@ -296,7 +293,7 @@ namespace Emby.Server.Implementations.Session
                     try
                     try
                     {
                     {
                         user.LastActivityDate = activityDate;
                         user.LastActivityDate = activityDate;
-                        _userManager.UpdateUser(user);
+                        await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
                     }
                     }
                     catch (DbUpdateConcurrencyException e)
                     catch (DbUpdateConcurrencyException e)
                     {
                     {
@@ -319,14 +316,14 @@ namespace Emby.Server.Implementations.Session
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void OnSessionControllerConnected(SessionInfo info)
+        public void OnSessionControllerConnected(SessionInfo session)
         {
         {
             EventHelper.QueueEventIfNotNull(
             EventHelper.QueueEventIfNotNull(
                 SessionControllerConnected,
                 SessionControllerConnected,
                 this,
                 this,
                 new SessionEventArgs
                 new SessionEventArgs
                 {
                 {
-                    SessionInfo = info
+                    SessionInfo = session
                 },
                 },
                 _logger);
                 _logger);
         }
         }
@@ -461,7 +458,7 @@ namespace Emby.Server.Implementations.Session
         /// <param name="remoteEndPoint">The remote end point.</param>
         /// <param name="remoteEndPoint">The remote end point.</param>
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <returns>SessionInfo.</returns>
         /// <returns>SessionInfo.</returns>
-        private SessionInfo GetSessionInfo(
+        private async Task<SessionInfo> GetSessionInfo(
             string appName,
             string appName,
             string appVersion,
             string appVersion,
             string deviceId,
             string deviceId,
@@ -480,9 +477,11 @@ namespace Emby.Server.Implementations.Session
 
 
             CheckDisposed();
             CheckDisposed();
 
 
-            var sessionInfo = _activeConnections.GetOrAdd(
-                key,
-                k => CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user));
+            if (!_activeConnections.TryGetValue(key, out var sessionInfo))
+            {
+                _activeConnections[key] = await CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false);
+                sessionInfo = _activeConnections[key];
+            }
 
 
             sessionInfo.UserId = user?.Id ?? Guid.Empty;
             sessionInfo.UserId = user?.Id ?? Guid.Empty;
             sessionInfo.UserName = user?.Username;
             sessionInfo.UserName = user?.Username;
@@ -505,7 +504,7 @@ namespace Emby.Server.Implementations.Session
             return sessionInfo;
             return sessionInfo;
         }
         }
 
 
-        private SessionInfo CreateSession(
+        private async Task<SessionInfo> CreateSession(
             string key,
             string key,
             string appName,
             string appName,
             string appVersion,
             string appVersion,
@@ -535,7 +534,7 @@ namespace Emby.Server.Implementations.Session
                 deviceName = "Network Device";
                 deviceName = "Network Device";
             }
             }
 
 
-            var deviceOptions = _deviceManager.GetDeviceOptions(deviceId);
+            var deviceOptions = await _deviceManager.GetDeviceOptions(deviceId).ConfigureAwait(false);
             if (string.IsNullOrEmpty(deviceOptions.CustomName))
             if (string.IsNullOrEmpty(deviceOptions.CustomName))
             {
             {
                 sessionInfo.DeviceName = deviceName;
                 sessionInfo.DeviceName = deviceName;
@@ -1433,41 +1432,23 @@ namespace Emby.Server.Implementations.Session
         /// <summary>
         /// <summary>
         /// Authenticates the new session.
         /// Authenticates the new session.
         /// </summary>
         /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>Task{SessionInfo}.</returns>
+        /// <param name="request">The authenticationrequest.</param>
+        /// <returns>The authentication result.</returns>
         public Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
         public Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
         {
         {
             return AuthenticateNewSessionInternal(request, true);
             return AuthenticateNewSessionInternal(request, true);
         }
         }
 
 
-        public Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request)
+        /// <summary>
+        /// Directly authenticates the session without enforcing password.
+        /// </summary>
+        /// <param name="request">The authentication request.</param>
+        /// <returns>The authentication result.</returns>
+        public Task<AuthenticationResult> AuthenticateDirect(AuthenticationRequest request)
         {
         {
             return AuthenticateNewSessionInternal(request, false);
             return AuthenticateNewSessionInternal(request, false);
         }
         }
 
 
-        public Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token)
-        {
-            var result = _authRepo.Get(new AuthenticationInfoQuery()
-            {
-                AccessToken = token,
-                DeviceId = _appHost.SystemId,
-                Limit = 1
-            });
-
-            if (result.TotalRecordCount == 0)
-            {
-                throw new SecurityException("Unknown quick connect token");
-            }
-
-            var info = result.Items[0];
-            request.UserId = info.UserId;
-
-            // There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token.
-            _authRepo.Delete(info);
-
-            return AuthenticateNewSessionInternal(request, false);
-        }
-
         private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)
         private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)
         {
         {
             CheckDisposed();
             CheckDisposed();
@@ -1510,15 +1491,15 @@ namespace Emby.Server.Implementations.Session
                 throw new SecurityException("User is at their maximum number of sessions.");
                 throw new SecurityException("User is at their maximum number of sessions.");
             }
             }
 
 
-            var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName);
+            var token = await GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false);
 
 
-            var session = LogSessionActivity(
+            var session = await LogSessionActivity(
                 request.App,
                 request.App,
                 request.AppVersion,
                 request.AppVersion,
                 request.DeviceId,
                 request.DeviceId,
                 request.DeviceName,
                 request.DeviceName,
                 request.RemoteEndPoint,
                 request.RemoteEndPoint,
-                user);
+                user).ConfigureAwait(false);
 
 
             var returnResult = new AuthenticationResult
             var returnResult = new AuthenticationResult
             {
             {
@@ -1533,36 +1514,33 @@ namespace Emby.Server.Implementations.Session
             return returnResult;
             return returnResult;
         }
         }
 
 
-        private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
+        private async Task<string> GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
         {
         {
-            var existing = _authRepo.Get(
-                new AuthenticationInfoQuery
+            var existing = (await _deviceManager.GetDevices(
+                new DeviceQuery
                 {
                 {
                     DeviceId = deviceId,
                     DeviceId = deviceId,
                     UserId = user.Id,
                     UserId = user.Id,
                     Limit = 1
                     Limit = 1
-                }).Items.FirstOrDefault();
+                }).ConfigureAwait(false)).Items.FirstOrDefault();
 
 
-            if (!string.IsNullOrEmpty(deviceId))
-            {
-                var allExistingForDevice = _authRepo.Get(
-                    new AuthenticationInfoQuery
-                    {
-                        DeviceId = deviceId
-                    }).Items;
+            var allExistingForDevice = (await _deviceManager.GetDevices(
+                new DeviceQuery
+                {
+                    DeviceId = deviceId
+                }).ConfigureAwait(false)).Items;
 
 
-                foreach (var auth in allExistingForDevice)
+            foreach (var auth in allExistingForDevice)
+            {
+                if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal))
                 {
                 {
-                    if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal))
+                    try
                     {
                     {
-                        try
-                        {
-                            Logout(auth);
-                        }
-                        catch (Exception ex)
-                        {
-                            _logger.LogError(ex, "Error while logging out.");
-                        }
+                        await Logout(auth).ConfigureAwait(false);
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "Error while logging out.");
                     }
                     }
                 }
                 }
             }
             }
@@ -1573,29 +1551,14 @@ namespace Emby.Server.Implementations.Session
                 return existing.AccessToken;
                 return existing.AccessToken;
             }
             }
 
 
-            var now = DateTime.UtcNow;
-
-            var newToken = new AuthenticationInfo
-            {
-                AppName = app,
-                AppVersion = appVersion,
-                DateCreated = now,
-                DateLastActivity = now,
-                DeviceId = deviceId,
-                DeviceName = deviceName,
-                UserId = user.Id,
-                AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
-                UserName = user.Username
-            };
-
             _logger.LogInformation("Creating new access token for user {0}", user.Id);
             _logger.LogInformation("Creating new access token for user {0}", user.Id);
-            _authRepo.Create(newToken);
+            var device = await _deviceManager.CreateDevice(new Device(user.Id, app, appVersion, deviceName, deviceId)).ConfigureAwait(false);
 
 
-            return newToken.AccessToken;
+            return device.AccessToken;
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Logout(string accessToken)
+        public async Task Logout(string accessToken)
         {
         {
             CheckDisposed();
             CheckDisposed();
 
 
@@ -1604,30 +1567,30 @@ namespace Emby.Server.Implementations.Session
                 throw new ArgumentNullException(nameof(accessToken));
                 throw new ArgumentNullException(nameof(accessToken));
             }
             }
 
 
-            var existing = _authRepo.Get(
-                new AuthenticationInfoQuery
+            var existing = (await _deviceManager.GetDevices(
+                new DeviceQuery
                 {
                 {
                     Limit = 1,
                     Limit = 1,
                     AccessToken = accessToken
                     AccessToken = accessToken
-                }).Items;
+                }).ConfigureAwait(false)).Items;
 
 
             if (existing.Count > 0)
             if (existing.Count > 0)
             {
             {
-                Logout(existing[0]);
+                await Logout(existing[0]).ConfigureAwait(false);
             }
             }
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Logout(AuthenticationInfo existing)
+        public async Task Logout(Device device)
         {
         {
             CheckDisposed();
             CheckDisposed();
 
 
-            _logger.LogInformation("Logging out access token {0}", existing.AccessToken);
+            _logger.LogInformation("Logging out access token {0}", device.AccessToken);
 
 
-            _authRepo.Delete(existing);
+            await _deviceManager.DeleteDevice(device).ConfigureAwait(false);
 
 
             var sessions = Sessions
             var sessions = Sessions
-                .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase))
+                .Where(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase))
                 .ToList();
                 .ToList();
 
 
             foreach (var session in sessions)
             foreach (var session in sessions)
@@ -1638,36 +1601,30 @@ namespace Emby.Server.Implementations.Session
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {
-                    _logger.LogError("Error reporting session ended", ex);
+                    _logger.LogError(ex, "Error reporting session ended");
                 }
                 }
             }
             }
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void RevokeUserTokens(Guid userId, string currentAccessToken)
+        public async Task RevokeUserTokens(Guid userId, string currentAccessToken)
         {
         {
             CheckDisposed();
             CheckDisposed();
 
 
-            var existing = _authRepo.Get(new AuthenticationInfoQuery
+            var existing = await _deviceManager.GetDevices(new DeviceQuery
             {
             {
                 UserId = userId
                 UserId = userId
-            });
+            }).ConfigureAwait(false);
 
 
             foreach (var info in existing.Items)
             foreach (var info in existing.Items)
             {
             {
                 if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase))
                 if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    Logout(info);
+                    await Logout(info).ConfigureAwait(false);
                 }
                 }
             }
             }
         }
         }
 
 
-        /// <inheritdoc />
-        public void RevokeToken(string token)
-        {
-            Logout(token);
-        }
-
         /// <summary>
         /// <summary>
         /// Reports the capabilities.
         /// Reports the capabilities.
         /// </summary>
         /// </summary>
@@ -1787,18 +1744,9 @@ namespace Emby.Server.Implementations.Session
             }
             }
 
 
             var item = _libraryManager.GetItemById(new Guid(itemId));
             var item = _libraryManager.GetItemById(new Guid(itemId));
-
-            var info = GetItemInfo(item, null);
-
-            ReportNowViewingItem(sessionId, info);
-        }
-
-        /// <inheritdoc />
-        public void ReportNowViewingItem(string sessionId, BaseItemDto item)
-        {
             var session = GetSession(sessionId);
             var session = GetSession(sessionId);
 
 
-            session.NowViewingItem = item;
+            session.NowViewingItem = GetItemInfo(item, null);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -1828,7 +1776,7 @@ namespace Emby.Server.Implementations.Session
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion)
+        public Task<SessionInfo> GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion)
         {
         {
             if (info == null)
             if (info == null)
             {
             {
@@ -1861,20 +1809,20 @@ namespace Emby.Server.Implementations.Session
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
+        public async Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
         {
         {
-            var items = _authRepo.Get(new AuthenticationInfoQuery
+            var items = (await _deviceManager.GetDevices(new DeviceQuery
             {
             {
                 AccessToken = token,
                 AccessToken = token,
                 Limit = 1
                 Limit = 1
-            }).Items;
+            }).ConfigureAwait(false)).Items;
 
 
             if (items.Count == 0)
             if (items.Count == 0)
             {
             {
                 return null;
                 return null;
             }
             }
 
 
-            return GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null);
+            return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />

+ 2 - 2
Emby.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.Session
         /// <inheritdoc />
         /// <inheritdoc />
         public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection)
         public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection)
         {
         {
-            var session = GetSession(connection.QueryString, connection.RemoteEndPoint.ToString());
+            var session = await GetSession(connection.QueryString, connection.RemoteEndPoint.ToString()).ConfigureAwait(false);
             if (session != null)
             if (session != null)
             {
             {
                 EnsureController(session, connection);
                 EnsureController(session, connection);
@@ -111,7 +111,7 @@ namespace Emby.Server.Implementations.Session
             }
             }
         }
         }
 
 
-        private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint)
+        private Task<SessionInfo> GetSession(IQueryCollection queryString, string remoteEndpoint)
         {
         {
             if (queryString == null)
             if (queryString == null)
             {
             {

+ 1 - 1
Emby.Server.Implementations/Sorting/ArtistComparer.cs

@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Sorting
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
         private static string? GetValue(BaseItem? x)
         private static string? GetValue(BaseItem? x)
         {
         {
-            if (!(x is Audio audio))
+            if (x is not Audio audio)
             {
             {
                 return string.Empty;
                 return string.Empty;
             }
             }

+ 11 - 11
Emby.Server.Implementations/TV/TVSeriesManager.cs

@@ -33,9 +33,9 @@ namespace Emby.Server.Implementations.TV
             _configurationManager = configurationManager;
             _configurationManager = configurationManager;
         }
         }
 
 
-        public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
+        public QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options)
         {
         {
-            var user = _userManager.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(query.UserId);
 
 
             if (user == null)
             if (user == null)
             {
             {
@@ -43,9 +43,9 @@ namespace Emby.Server.Implementations.TV
             }
             }
 
 
             string presentationUniqueKey = null;
             string presentationUniqueKey = null;
-            if (!string.IsNullOrEmpty(request.SeriesId))
+            if (!string.IsNullOrEmpty(query.SeriesId))
             {
             {
-                if (_libraryManager.GetItemById(request.SeriesId) is Series series)
+                if (_libraryManager.GetItemById(query.SeriesId) is Series series)
                 {
                 {
                     presentationUniqueKey = GetUniqueSeriesKey(series);
                     presentationUniqueKey = GetUniqueSeriesKey(series);
                 }
                 }
@@ -53,14 +53,14 @@ namespace Emby.Server.Implementations.TV
 
 
             if (!string.IsNullOrEmpty(presentationUniqueKey))
             if (!string.IsNullOrEmpty(presentationUniqueKey))
             {
             {
-                return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
+                return GetResult(GetNextUpEpisodes(query, user, new[] { presentationUniqueKey }, options), query);
             }
             }
 
 
             BaseItem[] parents;
             BaseItem[] parents;
 
 
-            if (request.ParentId.HasValue)
+            if (query.ParentId.HasValue)
             {
             {
-                var parent = _libraryManager.GetItemById(request.ParentId.Value);
+                var parent = _libraryManager.GetItemById(query.ParentId.Value);
 
 
                 if (parent != null)
                 if (parent != null)
                 {
                 {
@@ -79,10 +79,10 @@ namespace Emby.Server.Implementations.TV
                    .ToArray();
                    .ToArray();
             }
             }
 
 
-            return GetNextUp(request, parents, dtoOptions);
+            return GetNextUp(query, parents, options);
         }
         }
 
 
-        public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions dtoOptions)
+        public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options)
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
@@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.TV
 
 
             if (!string.IsNullOrEmpty(presentationUniqueKey))
             if (!string.IsNullOrEmpty(presentationUniqueKey))
             {
             {
-                return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
+                return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, options), request);
             }
             }
 
 
             if (limit.HasValue)
             if (limit.HasValue)
@@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.TV
                 .Select(GetUniqueSeriesKey);
                 .Select(GetUniqueSeriesKey);
 
 
             // Avoid implicitly captured closure
             // Avoid implicitly captured closure
-            var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
+            var episodes = GetNextUpEpisodes(request, user, items, options);
 
 
             return GetResult(episodes, request);
             return GetResult(episodes, request);
         }
         }

+ 5 - 5
Jellyfin.Api/Auth/CustomAuthenticationHandler.cs

@@ -40,11 +40,11 @@ namespace Jellyfin.Api.Auth
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
+        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
         {
         {
             try
             try
             {
             {
-                var authorizationInfo = _authService.Authenticate(Request);
+                var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false);
                 var role = UserRoles.User;
                 var role = UserRoles.User;
                 if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
                 if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
                 {
                 {
@@ -68,16 +68,16 @@ namespace Jellyfin.Api.Auth
                 var principal = new ClaimsPrincipal(identity);
                 var principal = new ClaimsPrincipal(identity);
                 var ticket = new AuthenticationTicket(principal, Scheme.Name);
                 var ticket = new AuthenticationTicket(principal, Scheme.Name);
 
 
-                return Task.FromResult(AuthenticateResult.Success(ticket));
+                return AuthenticateResult.Success(ticket);
             }
             }
             catch (AuthenticationException ex)
             catch (AuthenticationException ex)
             {
             {
                 _logger.LogDebug(ex, "Error authenticating with {Handler}", nameof(CustomAuthenticationHandler));
                 _logger.LogDebug(ex, "Error authenticating with {Handler}", nameof(CustomAuthenticationHandler));
-                return Task.FromResult(AuthenticateResult.NoResult());
+                return AuthenticateResult.NoResult();
             }
             }
             catch (SecurityException ex)
             catch (SecurityException ex)
             {
             {
-                return Task.FromResult(AuthenticateResult.Fail(ex));
+                return AuthenticateResult.Fail(ex);
             }
             }
         }
         }
     }
     }

+ 1 - 1
Jellyfin.Api/Controllers/ActivityLogController.cs

@@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers
         {
         {
             return await _activityManager.GetPagedResultAsync(new ActivityLogQuery
             return await _activityManager.GetPagedResultAsync(new ActivityLogQuery
             {
             {
-                StartIndex = startIndex,
+                Skip = startIndex,
                 Limit = limit,
                 Limit = limit,
                 MinDate = minDate,
                 MinDate = minDate,
                 HasUserId = hasUserId
                 HasUserId = hasUserId

+ 18 - 34
Jellyfin.Api/Controllers/ApiKeyController.cs

@@ -1,10 +1,8 @@
 using System;
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
-using System.Globalization;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
-using MediaBrowser.Controller;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Security;
-using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
@@ -18,24 +16,15 @@ namespace Jellyfin.Api.Controllers
     [Route("Auth")]
     [Route("Auth")]
     public class ApiKeyController : BaseJellyfinApiController
     public class ApiKeyController : BaseJellyfinApiController
     {
     {
-        private readonly ISessionManager _sessionManager;
-        private readonly IServerApplicationHost _appHost;
-        private readonly IAuthenticationRepository _authRepo;
+        private readonly IAuthenticationManager _authenticationManager;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ApiKeyController"/> class.
         /// Initializes a new instance of the <see cref="ApiKeyController"/> class.
         /// </summary>
         /// </summary>
-        /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
-        /// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
-        /// <param name="authRepo">Instance of <see cref="IAuthenticationRepository"/> interface.</param>
-        public ApiKeyController(
-            ISessionManager sessionManager,
-            IServerApplicationHost appHost,
-            IAuthenticationRepository authRepo)
+        /// <param name="authenticationManager">Instance of <see cref="IAuthenticationManager"/> interface.</param>
+        public ApiKeyController(IAuthenticationManager authenticationManager)
         {
         {
-            _sessionManager = sessionManager;
-            _appHost = appHost;
-            _authRepo = authRepo;
+            _authenticationManager = authenticationManager;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -46,14 +35,15 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Keys")]
         [HttpGet("Keys")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QueryResult<AuthenticationInfo>> GetKeys()
+        public async Task<ActionResult<QueryResult<AuthenticationInfo>>> GetKeys()
         {
         {
-            var result = _authRepo.Get(new AuthenticationInfoQuery
-            {
-                HasUser = false
-            });
+            var keys = await _authenticationManager.GetApiKeys();
 
 
-            return result;
+            return new QueryResult<AuthenticationInfo>
+            {
+                Items = keys,
+                TotalRecordCount = keys.Count
+            };
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -65,17 +55,10 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Keys")]
         [HttpPost("Keys")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult CreateKey([FromQuery, Required] string app)
+        public async Task<ActionResult> CreateKey([FromQuery, Required] string app)
         {
         {
-            _authRepo.Create(new AuthenticationInfo
-            {
-                AppName = app,
-                AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
-                DateCreated = DateTime.UtcNow,
-                DeviceId = _appHost.SystemId,
-                DeviceName = _appHost.FriendlyName,
-                AppVersion = _appHost.ApplicationVersionString
-            });
+            await _authenticationManager.CreateApiKey(app).ConfigureAwait(false);
+
             return NoContent();
             return NoContent();
         }
         }
 
 
@@ -88,9 +71,10 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("Keys/{key}")]
         [HttpDelete("Keys/{key}")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult RevokeKey([FromRoute, Required] string key)
+        public async Task<ActionResult> RevokeKey([FromRoute, Required] string key)
         {
         {
-            _sessionManager.RevokeToken(key);
+            await _authenticationManager.DeleteApiKey(key).ConfigureAwait(false);
+
             return NoContent();
             return NoContent();
         }
         }
     }
     }

+ 1 - 1
Jellyfin.Api/Controllers/CollectionController.cs

@@ -58,7 +58,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] Guid? parentId,
             [FromQuery] Guid? parentId,
             [FromQuery] bool isLocked = false)
             [FromQuery] bool isLocked = false)
         {
         {
-            var userId = _authContext.GetAuthorizationInfo(Request).UserId;
+            var userId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).UserId;
 
 
             var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
             var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
             {
             {

+ 19 - 29
Jellyfin.Api/Controllers/DevicesController.cs

@@ -1,8 +1,11 @@
 using System;
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
+using Jellyfin.Data.Dtos;
+using Jellyfin.Data.Entities.Security;
+using Jellyfin.Data.Queries;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
@@ -19,22 +22,18 @@ namespace Jellyfin.Api.Controllers
     public class DevicesController : BaseJellyfinApiController
     public class DevicesController : BaseJellyfinApiController
     {
     {
         private readonly IDeviceManager _deviceManager;
         private readonly IDeviceManager _deviceManager;
-        private readonly IAuthenticationRepository _authenticationRepository;
         private readonly ISessionManager _sessionManager;
         private readonly ISessionManager _sessionManager;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="DevicesController"/> class.
         /// Initializes a new instance of the <see cref="DevicesController"/> class.
         /// </summary>
         /// </summary>
         /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param>
         /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param>
-        /// <param name="authenticationRepository">Instance of <see cref="IAuthenticationRepository"/> interface.</param>
         /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
         /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
         public DevicesController(
         public DevicesController(
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
-            IAuthenticationRepository authenticationRepository,
             ISessionManager sessionManager)
             ISessionManager sessionManager)
         {
         {
             _deviceManager = deviceManager;
             _deviceManager = deviceManager;
-            _authenticationRepository = authenticationRepository;
             _sessionManager = sessionManager;
             _sessionManager = sessionManager;
         }
         }
 
 
@@ -47,10 +46,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
         /// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
         [HttpGet]
         [HttpGet]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
+        public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
         {
         {
-            var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty };
-            return _deviceManager.GetDevices(deviceQuery);
+            return await _deviceManager.GetDevicesForUser(userId, supportsSync).ConfigureAwait(false);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -63,9 +61,9 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Info")]
         [HttpGet("Info")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string id)
+        public async Task<ActionResult<DeviceInfo>> GetDeviceInfo([FromQuery, Required] string id)
         {
         {
-            var deviceInfo = _deviceManager.GetDevice(id);
+            var deviceInfo = await _deviceManager.GetDevice(id).ConfigureAwait(false);
             if (deviceInfo == null)
             if (deviceInfo == null)
             {
             {
                 return NotFound();
                 return NotFound();
@@ -84,9 +82,9 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Options")]
         [HttpGet("Options")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string id)
+        public async Task<ActionResult<DeviceOptions>> GetDeviceOptions([FromQuery, Required] string id)
         {
         {
-            var deviceInfo = _deviceManager.GetDeviceOptions(id);
+            var deviceInfo = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false);
             if (deviceInfo == null)
             if (deviceInfo == null)
             {
             {
                 return NotFound();
                 return NotFound();
@@ -101,22 +99,14 @@ namespace Jellyfin.Api.Controllers
         /// <param name="id">Device Id.</param>
         /// <param name="id">Device Id.</param>
         /// <param name="deviceOptions">Device Options.</param>
         /// <param name="deviceOptions">Device Options.</param>
         /// <response code="204">Device options updated.</response>
         /// <response code="204">Device options updated.</response>
-        /// <response code="404">Device not found.</response>
-        /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpPost("Options")]
         [HttpPost("Options")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult UpdateDeviceOptions(
+        public async Task<ActionResult> UpdateDeviceOptions(
             [FromQuery, Required] string id,
             [FromQuery, Required] string id,
-            [FromBody, Required] DeviceOptions deviceOptions)
+            [FromBody, Required] DeviceOptionsDto deviceOptions)
         {
         {
-            var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
-            if (existingDeviceOptions == null)
-            {
-                return NotFound();
-            }
-
-            _deviceManager.UpdateDeviceOptions(id, deviceOptions);
+            await _deviceManager.UpdateDeviceOptions(id, deviceOptions.CustomName).ConfigureAwait(false);
             return NoContent();
             return NoContent();
         }
         }
 
 
@@ -130,19 +120,19 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete]
         [HttpDelete]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteDevice([FromQuery, Required] string id)
+        public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id)
         {
         {
-            var existingDevice = _deviceManager.GetDevice(id);
+            var existingDevice = await _deviceManager.GetDevice(id).ConfigureAwait(false);
             if (existingDevice == null)
             if (existingDevice == null)
             {
             {
                 return NotFound();
                 return NotFound();
             }
             }
 
 
-            var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items;
+            var sessions = await _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }).ConfigureAwait(false);
 
 
-            foreach (var session in sessions)
+            foreach (var session in sessions.Items)
             {
             {
-                _sessionManager.Logout(session);
+                await _sessionManager.Logout(session).ConfigureAwait(false);
             }
             }
 
 
             return NoContent();
             return NoContent();

+ 4 - 4
Jellyfin.Api/Controllers/ImageController.cs

@@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
             [FromRoute, Required] ImageType imageType,
             [FromRoute, Required] ImageType imageType,
             [FromQuery] int? index = null)
             [FromQuery] int? index = null)
         {
         {
-            if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
+            if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
             {
             {
                 return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
                 return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
             }
             }
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
             [FromRoute, Required] ImageType imageType,
             [FromRoute, Required] ImageType imageType,
             [FromRoute] int index)
             [FromRoute] int index)
         {
         {
-            if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
+            if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
             {
             {
                 return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
                 return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
             }
             }
@@ -190,7 +190,7 @@ namespace Jellyfin.Api.Controllers
             [FromRoute, Required] ImageType imageType,
             [FromRoute, Required] ImageType imageType,
             [FromQuery] int? index = null)
             [FromQuery] int? index = null)
         {
         {
-            if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
+            if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
             {
             {
                 return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
                 return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
             }
             }
@@ -234,7 +234,7 @@ namespace Jellyfin.Api.Controllers
             [FromRoute, Required] ImageType imageType,
             [FromRoute, Required] ImageType imageType,
             [FromRoute] int index)
             [FromRoute] int index)
         {
         {
-            if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
+            if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
             {
             {
                 return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
                 return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
             }
             }

+ 5 - 5
Jellyfin.Api/Controllers/ItemUpdateController.cs

@@ -154,11 +154,11 @@ namespace Jellyfin.Api.Controllers
             };
             };
 
 
             if (!item.IsVirtualItem
             if (!item.IsVirtualItem
-                && !(item is ICollectionFolder)
-                && !(item is UserView)
-                && !(item is AggregateFolder)
-                && !(item is LiveTvChannel)
-                && !(item is IItemByName)
+                && item is not ICollectionFolder
+                && item is not UserView
+                && item is not AggregateFolder
+                && item is not LiveTvChannel
+                && item is not IItemByName
                 && item.SourceType == SourceType.Library)
                 && item.SourceType == SourceType.Library)
             {
             {
                 var inheritedContentType = _libraryManager.GetInheritedContentType(item);
                 var inheritedContentType = _libraryManager.GetInheritedContentType(item);

Some files were not shown because too many files changed in this diff