فهرست منبع

Merge branch 'master' into library-pictures

David Ullmer 4 سال پیش
والد
کامیت
b7c07f6821
100فایلهای تغییر یافته به همراه2011 افزوده شده و 3089 حذف شده
  1. 27 4
      .ci/azure-pipelines-package.yml
  2. 2 2
      Emby.Dlna/Eventing/DlnaEventManager.cs
  3. 1 1
      Emby.Dlna/IConnectionManager.cs
  4. 1 1
      Emby.Dlna/IContentDirectory.cs
  5. 1 1
      Emby.Dlna/IDlnaEventManager.cs
  6. 1 1
      Emby.Dlna/IMediaReceiverRegistrar.cs
  7. 1 1
      Emby.Dlna/PlayTo/PlayToController.cs
  8. 1 1
      Emby.Dlna/PlayTo/PlayToManager.cs
  9. 3 3
      Emby.Dlna/Service/BaseService.cs
  10. 1 1
      Emby.Dlna/Ssdp/DeviceDiscovery.cs
  11. 2 1
      Emby.Naming/Emby.Naming.csproj
  12. 1 1
      Emby.Notifications/NotificationEntryPoint.cs
  13. 0 590
      Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
  14. 82 77
      Emby.Server.Implementations/ApplicationHost.cs
  15. 15 6
      Emby.Server.Implementations/Channels/ChannelManager.cs
  16. 20 31
      Emby.Server.Implementations/Collections/CollectionManager.cs
  17. 1 1
      Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
  18. 1 1
      Emby.Server.Implementations/Devices/DeviceManager.cs
  19. 3 3
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  20. 1 1
      Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
  21. 1 1
      Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  22. 5 4
      Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
  23. 0 210
      Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs
  24. 1 1
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  25. 7 1
      Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
  26. 15 24
      Emby.Server.Implementations/Library/LibraryManager.cs
  27. 1 1
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  28. 1 1
      Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
  29. 11 7
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  30. 2 2
      Emby.Server.Implementations/Localization/Core/id.json
  31. 4 1
      Emby.Server.Implementations/Localization/Core/th.json
  32. 10 10
      Emby.Server.Implementations/Playlists/PlaylistManager.cs
  33. 1 1
      Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  34. 1 1
      Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
  35. 61 56
      Emby.Server.Implementations/Session/SessionManager.cs
  36. 1 1
      Emby.Server.Implementations/Session/SessionWebSocketListener.cs
  37. 8 7
      Jellyfin.Api/Controllers/CollectionController.cs
  38. 1 1
      Jellyfin.Api/Controllers/ConfigurationController.cs
  39. 4 4
      Jellyfin.Api/Controllers/DlnaServerController.cs
  40. 1 1
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  41. 9 9
      Jellyfin.Api/Controllers/ImageController.cs
  42. 4 3
      Jellyfin.Api/Controllers/ItemUpdateController.cs
  43. 4 2
      Jellyfin.Api/Controllers/ItemsController.cs
  44. 6 7
      Jellyfin.Api/Controllers/LibraryController.cs
  45. 5 7
      Jellyfin.Api/Controllers/LibraryStructureController.cs
  46. 2 2
      Jellyfin.Api/Controllers/MediaInfoController.cs
  47. 2 1
      Jellyfin.Api/Controllers/MoviesController.cs
  48. 7 7
      Jellyfin.Api/Controllers/PlaylistsController.cs
  49. 6 2
      Jellyfin.Api/Controllers/PluginsController.cs
  50. 1 1
      Jellyfin.Api/Controllers/RemoteImageController.cs
  51. 6 10
      Jellyfin.Api/Controllers/SessionController.cs
  52. 11 16
      Jellyfin.Api/Controllers/StartupController.cs
  53. 7 7
      Jellyfin.Api/Controllers/VideosController.cs
  54. 5 1
      Jellyfin.Api/Helpers/MediaInfoHelper.cs
  55. 3 3
      Jellyfin.Api/Models/LibraryStructureDto/AddVirtualFolderDto.cs
  56. 21 0
      Jellyfin.Api/Models/LibraryStructureDto/UpdateLibraryOptionsDto.cs
  57. 22 0
      Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs
  58. 1 1
      Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
  59. 1 1
      Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs
  60. 2 0
      Jellyfin.Data/DayOfWeekHelper.cs
  61. 4 1
      Jellyfin.Data/Entities/ActivityLog.cs
  62. 0 208
      Jellyfin.Data/Entities/Artwork.cs
  63. 0 68
      Jellyfin.Data/Entities/Book.cs
  64. 0 119
      Jellyfin.Data/Entities/BookMetadata.cs
  65. 0 275
      Jellyfin.Data/Entities/Chapter.cs
  66. 0 121
      Jellyfin.Data/Entities/Collection.cs
  67. 0 154
      Jellyfin.Data/Entities/CollectionItem.cs
  68. 0 157
      Jellyfin.Data/Entities/Company.cs
  69. 0 230
      Jellyfin.Data/Entities/CompanyMetadata.cs
  70. 0 67
      Jellyfin.Data/Entities/CustomItem.cs
  71. 0 77
      Jellyfin.Data/Entities/CustomItemMetadata.cs
  72. 0 114
      Jellyfin.Data/Entities/Episode.cs
  73. 0 192
      Jellyfin.Data/Entities/EpisodeMetadata.cs
  74. 0 160
      Jellyfin.Data/Entities/Genre.cs
  75. 4 1
      Jellyfin.Data/Entities/Group.cs
  76. 3 1
      Jellyfin.Data/Entities/ImageInfo.cs
  77. 3 1
      Jellyfin.Data/Entities/ItemDisplayPreferences.cs
  78. 81 0
      Jellyfin.Data/Entities/Libraries/Artwork.cs
  79. 28 0
      Jellyfin.Data/Entities/Libraries/Book.cs
  80. 55 0
      Jellyfin.Data/Entities/Libraries/BookMetadata.cs
  81. 102 0
      Jellyfin.Data/Entities/Libraries/Chapter.cs
  82. 55 0
      Jellyfin.Data/Entities/Libraries/Collection.cs
  83. 94 0
      Jellyfin.Data/Entities/Libraries/CollectionItem.cs
  84. 67 0
      Jellyfin.Data/Entities/Libraries/Company.cs
  85. 74 0
      Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
  86. 28 0
      Jellyfin.Data/Entities/Libraries/CustomItem.cs
  87. 36 0
      Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
  88. 52 0
      Jellyfin.Data/Entities/Libraries/Episode.cs
  89. 67 0
      Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
  90. 75 0
      Jellyfin.Data/Entities/Libraries/Genre.cs
  91. 76 0
      Jellyfin.Data/Entities/Libraries/Library.cs
  92. 63 0
      Jellyfin.Data/Entities/Libraries/LibraryItem.cs
  93. 94 0
      Jellyfin.Data/Entities/Libraries/MediaFile.cs
  94. 67 0
      Jellyfin.Data/Entities/Libraries/MediaFileStream.cs
  95. 165 0
      Jellyfin.Data/Entities/Libraries/Metadata.cs
  96. 67 0
      Jellyfin.Data/Entities/Libraries/MetadataProvider.cs
  97. 83 0
      Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
  98. 28 0
      Jellyfin.Data/Entities/Libraries/Movie.cs
  99. 85 0
      Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
  100. 29 0
      Jellyfin.Data/Entities/Libraries/MusicAlbum.cs

+ 27 - 4
.ci/azure-pipelines-package.yml

@@ -147,19 +147,42 @@ jobs:
   displayName: 'Publish NuGet packages'
   dependsOn:
   - BuildPackage
-  condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
+  condition: succeeded('BuildPackage')
 
   pool:
     vmImage: 'ubuntu-latest'
 
   steps:
-  - task: NuGetCommand@2
+  - task: DotNetCoreCLI@2
+    displayName: 'Build Stable Nuget packages'
+    condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
     inputs:
       command: 'pack'
-      packagesToPack: Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj
-      packDestination: '$(Build.ArtifactStagingDirectory)'
+      packagesToPack: 'Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj'
+      versioningScheme: 'off'
+
+  - task: DotNetCoreCLI@2
+    displayName: 'Build Unstable Nuget packages'
+    inputs:
+      command: 'custom'
+      projects: |
+        Jellyfin.Data/Jellyfin.Data.csproj
+        MediaBrowser.Common/MediaBrowser.Common.csproj
+        MediaBrowser.Controller/MediaBrowser.Controller.csproj
+        MediaBrowser.Model/MediaBrowser.Model.csproj
+        Emby.Naming/Emby.Naming.csproj
+      custom: 'pack'
+      arguments: '--version-suffix $(Build.BuildNumber) -o $(Build.ArtifactStagingDirectory)'
+
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish Nuget packages'
+    inputs:
+      pathToPublish: $(Build.ArtifactStagingDirectory)
+      artifactName: Jellyfin Nuget Packages
 
   - task: NuGetCommand@2
+    displayName: 'Push Nuget packages to feed'
+    condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
     inputs:
       command: 'push'
       packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'

+ 2 - 2
Emby.Dlna/Eventing/EventManager.cs → Emby.Dlna/Eventing/DlnaEventManager.cs

@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
 
 namespace Emby.Dlna.Eventing
 {
-    public class EventManager : IEventManager
+    public class DlnaEventManager : IDlnaEventManager
     {
         private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
             new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
@@ -24,7 +24,7 @@ namespace Emby.Dlna.Eventing
 
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
-        public EventManager(ILogger logger, IHttpClient httpClient)
+        public DlnaEventManager(ILogger logger, IHttpClient httpClient)
         {
             _httpClient = httpClient;
             _logger = logger;

+ 1 - 1
Emby.Dlna/IConnectionManager.cs

@@ -2,7 +2,7 @@
 
 namespace Emby.Dlna
 {
-    public interface IConnectionManager : IEventManager, IUpnpService
+    public interface IConnectionManager : IDlnaEventManager, IUpnpService
     {
     }
 }

+ 1 - 1
Emby.Dlna/IContentDirectory.cs

@@ -2,7 +2,7 @@
 
 namespace Emby.Dlna
 {
-    public interface IContentDirectory : IEventManager, IUpnpService
+    public interface IContentDirectory : IDlnaEventManager, IUpnpService
     {
     }
 }

+ 1 - 1
Emby.Dlna/IEventManager.cs → Emby.Dlna/IDlnaEventManager.cs

@@ -2,7 +2,7 @@
 
 namespace Emby.Dlna
 {
-    public interface IEventManager
+    public interface IDlnaEventManager
     {
         /// <summary>
         /// Cancels the event subscription.

+ 1 - 1
Emby.Dlna/IMediaReceiverRegistrar.cs

@@ -2,7 +2,7 @@
 
 namespace Emby.Dlna
 {
-    public interface IMediaReceiverRegistrar : IEventManager, IUpnpService
+    public interface IMediaReceiverRegistrar : IDlnaEventManager, IUpnpService
     {
     }
 }

+ 1 - 1
Emby.Dlna/PlayTo/PlayToController.cs

@@ -8,6 +8,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Emby.Dlna.Didl;
 using Jellyfin.Data.Entities;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
@@ -18,7 +19,6 @@ using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Session;
 using Microsoft.AspNetCore.WebUtilities;

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

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
@@ -16,7 +17,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Session;
 using Microsoft.Extensions.Logging;

+ 3 - 3
Emby.Dlna/Service/BaseService.cs

@@ -6,17 +6,17 @@ using Microsoft.Extensions.Logging;
 
 namespace Emby.Dlna.Service
 {
-    public class BaseService : IEventManager
+    public class BaseService : IDlnaEventManager
     {
         protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
         {
             Logger = logger;
             HttpClient = httpClient;
 
-            EventManager = new EventManager(logger, HttpClient);
+            EventManager = new DlnaEventManager(logger, HttpClient);
         }
 
-        protected IEventManager EventManager { get; }
+        protected IDlnaEventManager EventManager { get; }
 
         protected IHttpClient HttpClient { get; }
 

+ 1 - 1
Emby.Dlna/Ssdp/DeviceDiscovery.cs

@@ -3,9 +3,9 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Jellyfin.Data.Events;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
 using Rssdp;
 using Rssdp.Infrastructure;
 

+ 2 - 1
Emby.Naming/Emby.Naming.csproj

@@ -23,8 +23,9 @@
   <PropertyGroup>
     <Authors>Jellyfin Contributors</Authors>
     <PackageId>Jellyfin.Naming</PackageId>
-    <PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
+    <VersionPrefix>10.7.0</VersionPrefix>
     <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
+    <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
   </PropertyGroup>
 
   <!-- Code Analyzers-->

+ 1 - 1
Emby.Notifications/NotificationEntryPoint.cs

@@ -4,6 +4,7 @@ using System.Globalization;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
@@ -13,7 +14,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Notifications;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Model.Activity;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Notifications;
 using Microsoft.Extensions.Logging;

+ 0 - 590
Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs

@@ -1,590 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Common.Plugins;
-using MediaBrowser.Common.Updates;
-using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Controller.Subtitles;
-using MediaBrowser.Model.Activity;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Notifications;
-using MediaBrowser.Model.Tasks;
-using MediaBrowser.Model.Updates;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.Activity
-{
-    /// <summary>
-    /// Entry point for the activity logger.
-    /// </summary>
-    public sealed class ActivityLogEntryPoint : IServerEntryPoint
-    {
-        private readonly ILogger<ActivityLogEntryPoint> _logger;
-        private readonly IInstallationManager _installationManager;
-        private readonly ISessionManager _sessionManager;
-        private readonly ITaskManager _taskManager;
-        private readonly IActivityManager _activityManager;
-        private readonly ILocalizationManager _localization;
-        private readonly ISubtitleManager _subManager;
-        private readonly IUserManager _userManager;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="sessionManager">The session manager.</param>
-        /// <param name="taskManager">The task manager.</param>
-        /// <param name="activityManager">The activity manager.</param>
-        /// <param name="localization">The localization manager.</param>
-        /// <param name="installationManager">The installation manager.</param>
-        /// <param name="subManager">The subtitle manager.</param>
-        /// <param name="userManager">The user manager.</param>
-        public ActivityLogEntryPoint(
-            ILogger<ActivityLogEntryPoint> logger,
-            ISessionManager sessionManager,
-            ITaskManager taskManager,
-            IActivityManager activityManager,
-            ILocalizationManager localization,
-            IInstallationManager installationManager,
-            ISubtitleManager subManager,
-            IUserManager userManager)
-        {
-            _logger = logger;
-            _sessionManager = sessionManager;
-            _taskManager = taskManager;
-            _activityManager = activityManager;
-            _localization = localization;
-            _installationManager = installationManager;
-            _subManager = subManager;
-            _userManager = userManager;
-        }
-
-        /// <inheritdoc />
-        public Task RunAsync()
-        {
-            _taskManager.TaskCompleted += OnTaskCompleted;
-
-            _installationManager.PluginInstalled += OnPluginInstalled;
-            _installationManager.PluginUninstalled += OnPluginUninstalled;
-            _installationManager.PluginUpdated += OnPluginUpdated;
-            _installationManager.PackageInstallationFailed += OnPackageInstallationFailed;
-
-            _sessionManager.SessionStarted += OnSessionStarted;
-            _sessionManager.AuthenticationFailed += OnAuthenticationFailed;
-            _sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
-            _sessionManager.SessionEnded += OnSessionEnded;
-            _sessionManager.PlaybackStart += OnPlaybackStart;
-            _sessionManager.PlaybackStopped += OnPlaybackStopped;
-
-            _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
-
-            _userManager.OnUserCreated += OnUserCreated;
-            _userManager.OnUserPasswordChanged += OnUserPasswordChanged;
-            _userManager.OnUserDeleted += OnUserDeleted;
-            _userManager.OnUserLockedOut += OnUserLockedOut;
-
-            return Task.CompletedTask;
-        }
-
-        private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                    string.Format(
-                        CultureInfo.InvariantCulture,
-                        _localization.GetLocalizedString("UserLockedOutWithName"),
-                        e.Argument.Username),
-                    NotificationType.UserLockedOut.ToString(),
-                    e.Argument.Id)
-            {
-                LogSeverity = LogLevel.Error
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
-                    e.Provider,
-                    Notifications.NotificationEntryPoint.GetItemName(e.Item)),
-                "SubtitleDownloadFailure",
-                Guid.Empty)
-            {
-                ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
-                ShortOverview = e.Exception.Message
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
-        {
-            var item = e.MediaInfo;
-
-            if (item == null)
-            {
-                _logger.LogWarning("PlaybackStopped reported with null media info.");
-                return;
-            }
-
-            if (e.Item != null && e.Item.IsThemeMedia)
-            {
-                // Don't report theme song or local trailer playback
-                return;
-            }
-
-            if (e.Users.Count == 0)
-            {
-                return;
-            }
-
-            var user = e.Users[0];
-
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
-                    user.Username,
-                    GetItemName(item),
-                    e.DeviceName),
-                GetPlaybackStoppedNotificationType(item.MediaType),
-                user.Id))
-                .ConfigureAwait(false);
-        }
-
-        private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
-        {
-            var item = e.MediaInfo;
-
-            if (item == null)
-            {
-                _logger.LogWarning("PlaybackStart reported with null media info.");
-                return;
-            }
-
-            if (e.Item != null && e.Item.IsThemeMedia)
-            {
-                // Don't report theme song or local trailer playback
-                return;
-            }
-
-            if (e.Users.Count == 0)
-            {
-                return;
-            }
-
-            var user = e.Users.First();
-
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
-                    user.Username,
-                    GetItemName(item),
-                    e.DeviceName),
-                GetPlaybackNotificationType(item.MediaType),
-                user.Id))
-                .ConfigureAwait(false);
-        }
-
-        private static string GetItemName(BaseItemDto item)
-        {
-            var name = item.Name;
-
-            if (!string.IsNullOrEmpty(item.SeriesName))
-            {
-                name = item.SeriesName + " - " + name;
-            }
-
-            if (item.Artists != null && item.Artists.Count > 0)
-            {
-                name = item.Artists[0] + " - " + name;
-            }
-
-            return name;
-        }
-
-        private static string GetPlaybackNotificationType(string mediaType)
-        {
-            if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
-            {
-                return NotificationType.AudioPlayback.ToString();
-            }
-
-            if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
-            {
-                return NotificationType.VideoPlayback.ToString();
-            }
-
-            return null;
-        }
-
-        private static string GetPlaybackStoppedNotificationType(string mediaType)
-        {
-            if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
-            {
-                return NotificationType.AudioPlaybackStopped.ToString();
-            }
-
-            if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
-            {
-                return NotificationType.VideoPlaybackStopped.ToString();
-            }
-
-            return null;
-        }
-
-        private async void OnSessionEnded(object sender, SessionEventArgs e)
-        {
-            var session = e.SessionInfo;
-
-            if (string.IsNullOrEmpty(session.UserName))
-            {
-                return;
-            }
-
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("UserOfflineFromDevice"),
-                    session.UserName,
-                    session.DeviceName),
-                "SessionEnded",
-                session.UserId)
-            {
-                ShortOverview = string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("LabelIpAddressValue"),
-                    session.RemoteEndPoint),
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
-        {
-            var user = e.Argument.User;
-
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
-                    user.Name),
-                "AuthenticationSucceeded",
-                user.Id)
-            {
-                ShortOverview = string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("LabelIpAddressValue"),
-                    e.Argument.SessionInfo.RemoteEndPoint),
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
-                    e.Argument.Username),
-                "AuthenticationFailed",
-                Guid.Empty)
-            {
-                LogSeverity = LogLevel.Error,
-                ShortOverview = string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("LabelIpAddressValue"),
-                    e.Argument.RemoteEndPoint),
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("UserDeletedWithName"),
-                    e.Argument.Username),
-                "UserDeleted",
-                Guid.Empty))
-                .ConfigureAwait(false);
-        }
-
-        private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("UserPasswordChangedWithName"),
-                    e.Argument.Username),
-                "UserPasswordChanged",
-                e.Argument.Id))
-                .ConfigureAwait(false);
-        }
-
-        private async void OnUserCreated(object sender, GenericEventArgs<User> e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("UserCreatedWithName"),
-                    e.Argument.Username),
-                "UserCreated",
-                e.Argument.Id))
-                .ConfigureAwait(false);
-        }
-
-        private async void OnSessionStarted(object sender, SessionEventArgs e)
-        {
-            var session = e.SessionInfo;
-
-            if (string.IsNullOrEmpty(session.UserName))
-            {
-                return;
-            }
-
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("UserOnlineFromDevice"),
-                    session.UserName,
-                    session.DeviceName),
-                "SessionStarted",
-                session.UserId)
-            {
-                ShortOverview = string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("LabelIpAddressValue"),
-                    session.RemoteEndPoint)
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnPluginUpdated(object sender, InstallationInfo e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("PluginUpdatedWithName"),
-                    e.Name),
-                NotificationType.PluginUpdateInstalled.ToString(),
-                Guid.Empty)
-            {
-                ShortOverview = string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("VersionNumber"),
-                    e.Version),
-                Overview = e.Changelog
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnPluginUninstalled(object sender, IPlugin e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("PluginUninstalledWithName"),
-                    e.Name),
-                NotificationType.PluginUninstalled.ToString(),
-                Guid.Empty))
-                .ConfigureAwait(false);
-        }
-
-        private async void OnPluginInstalled(object sender, InstallationInfo e)
-        {
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("PluginInstalledWithName"),
-                    e.Name),
-                NotificationType.PluginInstalled.ToString(),
-                Guid.Empty)
-            {
-                ShortOverview = string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("VersionNumber"),
-                    e.Version)
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
-        {
-            var installationInfo = e.InstallationInfo;
-
-            await CreateLogEntry(new ActivityLog(
-                string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("NameInstallFailed"),
-                    installationInfo.Name),
-                NotificationType.InstallationFailed.ToString(),
-                Guid.Empty)
-            {
-                ShortOverview = string.Format(
-                    CultureInfo.InvariantCulture,
-                    _localization.GetLocalizedString("VersionNumber"),
-                    installationInfo.Version),
-                Overview = e.Exception.Message
-            }).ConfigureAwait(false);
-        }
-
-        private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
-        {
-            var result = e.Result;
-            var task = e.Task;
-
-            if (task.ScheduledTask is IConfigurableScheduledTask activityTask
-                && !activityTask.IsLogged)
-            {
-                return;
-            }
-
-            var time = result.EndTimeUtc - result.StartTimeUtc;
-            var runningTime = string.Format(
-                CultureInfo.InvariantCulture,
-                _localization.GetLocalizedString("LabelRunningTimeValue"),
-                ToUserFriendlyString(time));
-
-            if (result.Status == TaskCompletionStatus.Failed)
-            {
-                var vals = new List<string>();
-
-                if (!string.IsNullOrEmpty(e.Result.ErrorMessage))
-                {
-                    vals.Add(e.Result.ErrorMessage);
-                }
-
-                if (!string.IsNullOrEmpty(e.Result.LongErrorMessage))
-                {
-                    vals.Add(e.Result.LongErrorMessage);
-                }
-
-                await CreateLogEntry(new ActivityLog(
-                    string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
-                    NotificationType.TaskFailed.ToString(),
-                    Guid.Empty)
-                {
-                    LogSeverity = LogLevel.Error,
-                    Overview = string.Join(Environment.NewLine, vals),
-                    ShortOverview = runningTime
-                }).ConfigureAwait(false);
-            }
-        }
-
-        private async Task CreateLogEntry(ActivityLog entry)
-            => await _activityManager.CreateAsync(entry).ConfigureAwait(false);
-
-        /// <inheritdoc />
-        public void Dispose()
-        {
-            _taskManager.TaskCompleted -= OnTaskCompleted;
-
-            _installationManager.PluginInstalled -= OnPluginInstalled;
-            _installationManager.PluginUninstalled -= OnPluginUninstalled;
-            _installationManager.PluginUpdated -= OnPluginUpdated;
-            _installationManager.PackageInstallationFailed -= OnPackageInstallationFailed;
-
-            _sessionManager.SessionStarted -= OnSessionStarted;
-            _sessionManager.AuthenticationFailed -= OnAuthenticationFailed;
-            _sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded;
-            _sessionManager.SessionEnded -= OnSessionEnded;
-
-            _sessionManager.PlaybackStart -= OnPlaybackStart;
-            _sessionManager.PlaybackStopped -= OnPlaybackStopped;
-
-            _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
-
-            _userManager.OnUserCreated -= OnUserCreated;
-            _userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
-            _userManager.OnUserDeleted -= OnUserDeleted;
-            _userManager.OnUserLockedOut -= OnUserLockedOut;
-        }
-
-        /// <summary>
-        /// Constructs a user-friendly string for this TimeSpan instance.
-        /// </summary>
-        private static string ToUserFriendlyString(TimeSpan span)
-        {
-            const int DaysInYear = 365;
-            const int DaysInMonth = 30;
-
-            // Get each non-zero value from TimeSpan component
-            var values = new List<string>();
-
-            // Number of years
-            int days = span.Days;
-            if (days >= DaysInYear)
-            {
-                int years = days / DaysInYear;
-                values.Add(CreateValueString(years, "year"));
-                days %= DaysInYear;
-            }
-
-            // Number of months
-            if (days >= DaysInMonth)
-            {
-                int months = days / DaysInMonth;
-                values.Add(CreateValueString(months, "month"));
-                days = days % DaysInMonth;
-            }
-
-            // Number of days
-            if (days >= 1)
-            {
-                values.Add(CreateValueString(days, "day"));
-            }
-
-            // Number of hours
-            if (span.Hours >= 1)
-            {
-                values.Add(CreateValueString(span.Hours, "hour"));
-            }
-
-            // Number of minutes
-            if (span.Minutes >= 1)
-            {
-                values.Add(CreateValueString(span.Minutes, "minute"));
-            }
-
-            // Number of seconds (include when 0 if no other components included)
-            if (span.Seconds >= 1 || values.Count == 0)
-            {
-                values.Add(CreateValueString(span.Seconds, "second"));
-            }
-
-            // Combine values into string
-            var builder = new StringBuilder();
-            for (int i = 0; i < values.Count; i++)
-            {
-                if (builder.Length > 0)
-                {
-                    builder.Append(i == values.Count - 1 ? " and " : ", ");
-                }
-
-                builder.Append(values[i]);
-            }
-
-            // Return result
-            return builder.ToString();
-        }
-
-        /// <summary>
-        /// Constructs a string description of a time-span value.
-        /// </summary>
-        /// <param name="value">The value of this item.</param>
-        /// <param name="description">The name of this item (singular form).</param>
-        private static string CreateValueString(int value, string description)
-        {
-            return string.Format(
-                CultureInfo.InvariantCulture,
-                "{0:#,##0} {1}",
-                value,
-                value == 1 ? description : string.Format(CultureInfo.InvariantCulture, "{0}s", description));
-        }
-    }
-}

+ 82 - 77
Emby.Server.Implementations/ApplicationHost.cs

@@ -53,7 +53,6 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Controller;
-using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Collections;
@@ -173,6 +172,8 @@ namespace Emby.Server.Implementations
         /// </summary>
         protected ILogger<ApplicationHost> Logger { get; }
 
+        protected IServiceCollection ServiceCollection { get; }
+
         private IPlugin[] _plugins;
 
         /// <summary>
@@ -238,9 +239,11 @@ namespace Emby.Server.Implementations
             ILoggerFactory loggerFactory,
             IStartupOptions options,
             IFileSystem fileSystem,
-            INetworkManager networkManager)
+            INetworkManager networkManager,
+            IServiceCollection serviceCollection)
         {
             _xmlSerializer = new MyXmlSerializer();
+            ServiceCollection = serviceCollection;
 
             _networkManager = networkManager;
             networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
@@ -464,7 +467,7 @@ namespace Emby.Server.Implementations
         }
 
         /// <inheritdoc/>
-        public void Init(IServiceCollection serviceCollection)
+        public void Init()
         {
             HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
             HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@@ -493,7 +496,7 @@ namespace Emby.Server.Implementations
 
             DiscoverTypes();
 
-            RegisterServices(serviceCollection);
+            RegisterServices();
         }
 
         public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
@@ -502,139 +505,139 @@ namespace Emby.Server.Implementations
         /// <summary>
         /// Registers services/resources with the service collection that will be available via DI.
         /// </summary>
-        protected virtual void RegisterServices(IServiceCollection serviceCollection)
+        protected virtual void RegisterServices()
         {
-            serviceCollection.AddSingleton(_startupOptions);
+            ServiceCollection.AddSingleton(_startupOptions);
 
-            serviceCollection.AddMemoryCache();
+            ServiceCollection.AddMemoryCache();
 
-            serviceCollection.AddSingleton(ConfigurationManager);
-            serviceCollection.AddSingleton<IApplicationHost>(this);
+            ServiceCollection.AddSingleton(ConfigurationManager);
+            ServiceCollection.AddSingleton<IApplicationHost>(this);
 
-            serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
+            ServiceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
 
-            serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
+            ServiceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
 
-            serviceCollection.AddSingleton(_fileSystemManager);
-            serviceCollection.AddSingleton<TvdbClientManager>();
+            ServiceCollection.AddSingleton(_fileSystemManager);
+            ServiceCollection.AddSingleton<TvdbClientManager>();
 
-            serviceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
+            ServiceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
 
-            serviceCollection.AddSingleton(_networkManager);
+            ServiceCollection.AddSingleton(_networkManager);
 
-            serviceCollection.AddSingleton<IIsoManager, IsoManager>();
+            ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
 
-            serviceCollection.AddSingleton<ITaskManager, TaskManager>();
+            ServiceCollection.AddSingleton<ITaskManager, TaskManager>();
 
-            serviceCollection.AddSingleton(_xmlSerializer);
+            ServiceCollection.AddSingleton(_xmlSerializer);
 
-            serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
+            ServiceCollection.AddSingleton<IStreamHelper, StreamHelper>();
 
-            serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
+            ServiceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
 
-            serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
+            ServiceCollection.AddSingleton<ISocketFactory, SocketFactory>();
 
-            serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
+            ServiceCollection.AddSingleton<IInstallationManager, InstallationManager>();
 
-            serviceCollection.AddSingleton<IZipClient, ZipClient>();
+            ServiceCollection.AddSingleton<IZipClient, ZipClient>();
 
-            serviceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>();
+            ServiceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>();
 
-            serviceCollection.AddSingleton<IServerApplicationHost>(this);
-            serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
+            ServiceCollection.AddSingleton<IServerApplicationHost>(this);
+            ServiceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
 
-            serviceCollection.AddSingleton(ServerConfigurationManager);
+            ServiceCollection.AddSingleton(ServerConfigurationManager);
 
-            serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
+            ServiceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
 
-            serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
+            ServiceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
 
-            serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
-            serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
+            ServiceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
+            ServiceCollection.AddSingleton<IUserDataManager, UserDataManager>();
 
-            serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
+            ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
 
-            serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
+            ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
 
             // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
-            serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
+            ServiceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
 
             // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
-            serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
-            serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
+            ServiceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
+            ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
 
             // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
-            serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
-            serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
-            serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
-            serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
+            ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
+            ServiceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
+            ServiceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
+            ServiceCollection.AddSingleton<ILibraryManager, LibraryManager>();
 
-            serviceCollection.AddSingleton<IMusicManager, MusicManager>();
+            ServiceCollection.AddSingleton<IMusicManager, MusicManager>();
 
-            serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
+            ServiceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
 
-            serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
+            ServiceCollection.AddSingleton<ISearchEngine, SearchEngine>();
 
-            serviceCollection.AddSingleton<ServiceController>();
-            serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
+            ServiceCollection.AddSingleton<ServiceController>();
+            ServiceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
 
-            serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
+            ServiceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
 
-            serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
+            ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
 
-            serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
+            ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>();
 
-            serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
+            ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
 
-            serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
+            ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
 
-            serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
+            ServiceCollection.AddSingleton<IProviderManager, ProviderManager>();
 
             // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
-            serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
-            serviceCollection.AddSingleton<IDtoService, DtoService>();
+            ServiceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
+            ServiceCollection.AddSingleton<IDtoService, DtoService>();
 
-            serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
+            ServiceCollection.AddSingleton<IChannelManager, ChannelManager>();
 
-            serviceCollection.AddSingleton<ISessionManager, SessionManager>();
+            ServiceCollection.AddSingleton<ISessionManager, SessionManager>();
 
-            serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
+            ServiceCollection.AddSingleton<IDlnaManager, DlnaManager>();
 
-            serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
+            ServiceCollection.AddSingleton<ICollectionManager, CollectionManager>();
 
-            serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
+            ServiceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
 
-            serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
+            ServiceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
 
-            serviceCollection.AddSingleton<LiveTvDtoService>();
-            serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
+            ServiceCollection.AddSingleton<LiveTvDtoService>();
+            ServiceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
 
-            serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
+            ServiceCollection.AddSingleton<IUserViewManager, UserViewManager>();
 
-            serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
+            ServiceCollection.AddSingleton<INotificationManager, NotificationManager>();
 
-            serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
+            ServiceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
 
-            serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
+            ServiceCollection.AddSingleton<IChapterManager, ChapterManager>();
 
-            serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
+            ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
 
-            serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
-            serviceCollection.AddSingleton<ISessionContext, SessionContext>();
+            ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
+            ServiceCollection.AddSingleton<ISessionContext, SessionContext>();
 
-            serviceCollection.AddSingleton<IAuthService, AuthService>();
+            ServiceCollection.AddSingleton<IAuthService, AuthService>();
 
-            serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
+            ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
 
-            serviceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>();
-            serviceCollection.AddSingleton<EncodingHelper>();
+            ServiceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>();
+            ServiceCollection.AddSingleton<EncodingHelper>();
 
-            serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
+            ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
 
-            serviceCollection.AddSingleton<TranscodingJobHelper>();
-            serviceCollection.AddScoped<MediaInfoHelper>();
-            serviceCollection.AddScoped<AudioHelper>();
-            serviceCollection.AddScoped<DynamicHlsHelper>();
+            ServiceCollection.AddSingleton<TranscodingJobHelper>();
+            ServiceCollection.AddScoped<MediaInfoHelper>();
+            ServiceCollection.AddScoped<AudioHelper>();
+            ServiceCollection.AddScoped<DynamicHlsHelper>();
         }
 
         /// <summary>
@@ -834,6 +837,8 @@ namespace Emby.Server.Implementations
                 {
                     hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s));
                 }
+
+                plugin.RegisterServices(ServiceCollection);
             }
             catch (Exception ex)
             {

+ 15 - 6
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -746,12 +746,21 @@ namespace Emby.Server.Implementations.Channels
             // null if came from cache
             if (itemsResult != null)
             {
-                var internalItems = itemsResult.Items
-                    .Select(i => GetChannelItemEntity(i, channelProvider, channel.Id, parentItem, cancellationToken))
-                    .ToArray();
+                var items = itemsResult.Items;
+                var itemsLen = items.Count;
+                var internalItems = new Guid[itemsLen];
+                for (int i = 0; i < itemsLen; i++)
+                {
+                    internalItems[i] = (await GetChannelItemEntityAsync(
+                        items[i],
+                        channelProvider,
+                        channel.Id,
+                        parentItem,
+                        cancellationToken).ConfigureAwait(false)).Id;
+                }
 
                 var existingIds = _libraryManager.GetItemIds(query);
-                var deadIds = existingIds.Except(internalItems.Select(i => i.Id))
+                var deadIds = existingIds.Except(internalItems)
                     .ToArray();
 
                 foreach (var deadId in deadIds)
@@ -963,7 +972,7 @@ namespace Emby.Server.Implementations.Channels
             return item;
         }
 
-        private BaseItem GetChannelItemEntity(ChannelItemInfo info, IChannel channelProvider, Guid internalChannelId, BaseItem parentFolder, CancellationToken cancellationToken)
+        private async Task<BaseItem> GetChannelItemEntityAsync(ChannelItemInfo info, IChannel channelProvider, Guid internalChannelId, BaseItem parentFolder, CancellationToken cancellationToken)
         {
             var parentFolderId = parentFolder.Id;
 
@@ -1165,7 +1174,7 @@ namespace Emby.Server.Implementations.Channels
             }
             else if (forceUpdate)
             {
-                item.UpdateToRepository(ItemUpdateType.None, cancellationToken);
+                await item.UpdateToRepositoryAsync(ItemUpdateType.None, cancellationToken).ConfigureAwait(false);
             }
 
             if ((isNew || forceUpdate) && info.Type == ChannelItemType.Media)

+ 20 - 31
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Collections
         }
 
         /// <inheritdoc />
-        public BoxSet CreateCollection(CollectionCreationOptions options)
+        public async Task<BoxSet> CreateCollectionAsync(CollectionCreationOptions options)
         {
             var name = options.Name;
 
@@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.Collections
             // This could cause it to get re-resolved as a plain folder
             var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
 
-            var parentFolder = GetCollectionsFolder(true).GetAwaiter().GetResult();
+            var parentFolder = await GetCollectionsFolder(true).ConfigureAwait(false);
 
             if (parentFolder == null)
             {
@@ -169,12 +169,16 @@ namespace Emby.Server.Implementations.Collections
 
                 if (options.ItemIdList.Length > 0)
                 {
-                    AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
-                    {
-                        // The initial adding of items is going to create a local metadata file
-                        // This will cause internet metadata to be skipped as a result
-                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh
-                    });
+                    await AddToCollectionAsync(
+                        collection.Id,
+                        options.ItemIdList.Select(x => new Guid(x)),
+                        false,
+                        new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+                        {
+                            // The initial adding of items is going to create a local metadata file
+                            // This will cause internet metadata to be skipped as a result
+                            MetadataRefreshMode = MetadataRefreshMode.FullRefresh
+                        }).ConfigureAwait(false);
                 }
                 else
                 {
@@ -197,18 +201,10 @@ namespace Emby.Server.Implementations.Collections
         }
 
         /// <inheritdoc />
-        public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
-        {
-            AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
-        }
+        public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids)
+            => AddToCollectionAsync(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
 
-        /// <inheritdoc />
-        public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
-        {
-            AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
-        }
-
-        private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
+        private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         {
             var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
             if (collection == null)
@@ -224,15 +220,14 @@ namespace Emby.Server.Implementations.Collections
 
             foreach (var id in ids)
             {
-                var guidId = new Guid(id);
-                var item = _libraryManager.GetItemById(guidId);
+                var item = _libraryManager.GetItemById(id);
 
                 if (item == null)
                 {
                     throw new ArgumentException("No item exists with the supplied Id");
                 }
 
-                if (!currentLinkedChildrenIds.Contains(guidId))
+                if (!currentLinkedChildrenIds.Contains(id))
                 {
                     itemList.Add(item);
 
@@ -249,7 +244,7 @@ namespace Emby.Server.Implementations.Collections
 
                 collection.UpdateRatingToItems(linkedChildrenList);
 
-                collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
                 refreshOptions.ForceSave = true;
                 _providerManager.QueueRefresh(collection.Id, refreshOptions, RefreshPriority.High);
@@ -266,13 +261,7 @@ namespace Emby.Server.Implementations.Collections
         }
 
         /// <inheritdoc />
-        public void RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds)
-        {
-            RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i)));
-        }
-
-        /// <inheritdoc />
-        public void RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
+        public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
         {
             var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
 
@@ -309,7 +298,7 @@ namespace Emby.Server.Implementations.Collections
                 collection.LinkedChildren = collection.LinkedChildren.Except(list).ToArray();
             }
 
-            collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             _providerManager.QueueRefresh(
                 collection.Id,
                 new MetadataRefreshOptions(new DirectoryService(_fileSystem))

+ 1 - 1
Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs

@@ -2,11 +2,11 @@ using System;
 using System.Globalization;
 using System.IO;
 using Emby.Server.Implementations.AppBase;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using Microsoft.Extensions.Logging;

+ 1 - 1
Emby.Server.Implementations/Devices/DeviceManager.cs

@@ -7,13 +7,13 @@ using System.IO;
 using System.Linq;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Model.Devices;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Session;

+ 3 - 3
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -22,7 +22,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="IPNetwork2" Version="2.5.211" />
+    <PackageReference Include="IPNetwork2" Version="2.5.224" />
     <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
     <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
     <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
@@ -37,11 +37,11 @@
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
     <PackageReference Include="Mono.Nat" Version="2.0.2" />
-    <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
+    <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
     <PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
     <PackageReference Include="sharpcompress" Version="0.26.0" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
-    <PackageReference Include="DotNet.Glob" Version="3.0.9" />
+    <PackageReference Include="DotNet.Glob" Version="3.1.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -7,11 +7,11 @@ using System.Net;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Events;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
 using Microsoft.Extensions.Logging;
 using Mono.Nat;
 

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

@@ -7,6 +7,7 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Data.Entities;
+using Jellyfin.Data.Events;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -15,7 +16,6 @@ using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
 using Microsoft.Extensions.Logging;
 
 namespace Emby.Server.Implementations.EntryPoints

+ 5 - 4
Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Data.Enums;
+using Jellyfin.Data.Events;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Plugins;
@@ -43,22 +44,22 @@ namespace Emby.Server.Implementations.EntryPoints
             return Task.CompletedTask;
         }
 
-        private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+        private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e)
         {
             await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false);
         }
 
-        private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+        private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e)
         {
             await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false);
         }
 
-        private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+        private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e)
         {
             await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false);
         }
 
-        private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+        private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e)
         {
             await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false);
         }

+ 0 - 210
Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs

@@ -1,210 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Common.Plugins;
-using MediaBrowser.Common.Updates;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Tasks;
-using MediaBrowser.Model.Updates;
-
-namespace Emby.Server.Implementations.EntryPoints
-{
-    /// <summary>
-    /// Class WebSocketEvents.
-    /// </summary>
-    public class ServerEventNotifier : IServerEntryPoint
-    {
-        /// <summary>
-        /// The user manager.
-        /// </summary>
-        private readonly IUserManager _userManager;
-
-        /// <summary>
-        /// The installation manager.
-        /// </summary>
-        private readonly IInstallationManager _installationManager;
-
-        /// <summary>
-        /// The kernel.
-        /// </summary>
-        private readonly IServerApplicationHost _appHost;
-
-        /// <summary>
-        /// The task manager.
-        /// </summary>
-        private readonly ITaskManager _taskManager;
-
-        private readonly ISessionManager _sessionManager;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ServerEventNotifier"/> class.
-        /// </summary>
-        /// <param name="appHost">The application host.</param>
-        /// <param name="userManager">The user manager.</param>
-        /// <param name="installationManager">The installation manager.</param>
-        /// <param name="taskManager">The task manager.</param>
-        /// <param name="sessionManager">The session manager.</param>
-        public ServerEventNotifier(
-            IServerApplicationHost appHost,
-            IUserManager userManager,
-            IInstallationManager installationManager,
-            ITaskManager taskManager,
-            ISessionManager sessionManager)
-        {
-            _userManager = userManager;
-            _installationManager = installationManager;
-            _appHost = appHost;
-            _taskManager = taskManager;
-            _sessionManager = sessionManager;
-        }
-
-        /// <inheritdoc />
-        public Task RunAsync()
-        {
-            _userManager.OnUserDeleted += OnUserDeleted;
-            _userManager.OnUserUpdated += OnUserUpdated;
-
-            _appHost.HasPendingRestartChanged += OnHasPendingRestartChanged;
-
-            _installationManager.PluginUninstalled += OnPluginUninstalled;
-            _installationManager.PackageInstalling += OnPackageInstalling;
-            _installationManager.PackageInstallationCancelled += OnPackageInstallationCancelled;
-            _installationManager.PackageInstallationCompleted += OnPackageInstallationCompleted;
-            _installationManager.PackageInstallationFailed += OnPackageInstallationFailed;
-
-            _taskManager.TaskCompleted += OnTaskCompleted;
-
-            return Task.CompletedTask;
-        }
-
-        private async void OnPackageInstalling(object sender, InstallationInfo e)
-        {
-            await SendMessageToAdminSessions("PackageInstalling", e).ConfigureAwait(false);
-        }
-
-        private async void OnPackageInstallationCancelled(object sender, InstallationInfo e)
-        {
-            await SendMessageToAdminSessions("PackageInstallationCancelled", e).ConfigureAwait(false);
-        }
-
-        private async void OnPackageInstallationCompleted(object sender, InstallationInfo e)
-        {
-            await SendMessageToAdminSessions("PackageInstallationCompleted", e).ConfigureAwait(false);
-        }
-
-        private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
-        {
-            await SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo).ConfigureAwait(false);
-        }
-
-        private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
-        {
-            await SendMessageToAdminSessions("ScheduledTaskEnded", e.Result).ConfigureAwait(false);
-        }
-
-        /// <summary>
-        /// Installations the manager_ plugin uninstalled.
-        /// </summary>
-        /// <param name="sender">The sender.</param>
-        /// <param name="e">The e.</param>
-        private async void OnPluginUninstalled(object sender, IPlugin e)
-        {
-            await SendMessageToAdminSessions("PluginUninstalled", e).ConfigureAwait(false);
-        }
-
-        /// <summary>
-        /// Handles the HasPendingRestartChanged event of the kernel control.
-        /// </summary>
-        /// <param name="sender">The source of the event.</param>
-        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
-        private async void OnHasPendingRestartChanged(object sender, EventArgs e)
-        {
-            await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false);
-        }
-
-        /// <summary>
-        /// Users the manager_ user updated.
-        /// </summary>
-        /// <param name="sender">The sender.</param>
-        /// <param name="e">The e.</param>
-        private async void OnUserUpdated(object sender, GenericEventArgs<User> e)
-        {
-            var dto = _userManager.GetUserDto(e.Argument);
-
-            await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false);
-        }
-
-        /// <summary>
-        /// Users the manager_ user deleted.
-        /// </summary>
-        /// <param name="sender">The sender.</param>
-        /// <param name="e">The e.</param>
-        private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
-        {
-            await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false);
-        }
-
-        private async Task SendMessageToAdminSessions<T>(string name, T data)
-        {
-            try
-            {
-                await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None).ConfigureAwait(false);
-            }
-            catch (Exception)
-            {
-            }
-        }
-
-        private async Task SendMessageToUserSession<T>(User user, string name, T data)
-        {
-            try
-            {
-                await _sessionManager.SendMessageToUserSessions(
-                    new List<Guid> { user.Id },
-                    name,
-                    data,
-                    CancellationToken.None).ConfigureAwait(false);
-            }
-            catch (Exception)
-            {
-            }
-        }
-
-        /// <inheritdoc />
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-                _userManager.OnUserDeleted -= OnUserDeleted;
-                _userManager.OnUserUpdated -= OnUserUpdated;
-
-                _installationManager.PluginUninstalled -= OnPluginUninstalled;
-                _installationManager.PackageInstalling -= OnPackageInstalling;
-                _installationManager.PackageInstallationCancelled -= OnPackageInstallationCancelled;
-                _installationManager.PackageInstallationCompleted -= OnPackageInstallationCompleted;
-                _installationManager.PackageInstallationFailed -= OnPackageInstallationFailed;
-
-                _appHost.HasPendingRestartChanged -= OnHasPendingRestartChanged;
-
-                _taskManager.TaskCompleted -= OnTaskCompleted;
-            }
-        }
-    }
-}

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

@@ -12,13 +12,13 @@ using System.Threading;
 using System.Threading.Tasks;
 using Emby.Server.Implementations.Services;
 using Emby.Server.Implementations.SocketSharp;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Services;

+ 7 - 1
Emby.Server.Implementations/HttpServer/WebSocketConnection.cs

@@ -179,7 +179,7 @@ namespace Emby.Server.Implementations.HttpServer
                 return;
             }
 
-            WebSocketMessage<object> stub;
+            WebSocketMessage<object>? stub;
             try
             {
 
@@ -209,6 +209,12 @@ namespace Emby.Server.Implementations.HttpServer
                 return;
             }
 
+            if (stub == null)
+            {
+                _logger.LogError("Error processing web socket message");
+                return;
+            }
+
             // Tell the PipeReader how much of the buffer we have consumed
             reader.AdvanceTo(buffer.End);
 

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

@@ -771,7 +771,7 @@ namespace Emby.Server.Implementations.Library
             if (folder.ParentId != rootFolder.Id)
             {
                 folder.ParentId = rootFolder.Id;
-                folder.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
+                folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
             }
 
             rootFolder.AddVirtualChild(folder);
@@ -1868,7 +1868,8 @@ namespace Emby.Server.Implementations.Library
             return image.Path != null && !image.IsLocalFile;
         }
 
-        public void UpdateImages(BaseItem item, bool forceUpdate = false)
+        /// <inheritdoc />
+        public async Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false)
         {
             if (item == null)
             {
@@ -1891,7 +1892,7 @@ namespace Emby.Server.Implementations.Library
                     try
                     {
                         var index = item.GetImageIndex(img);
-                        image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult();
+                        image = await ConvertImageToLocal(item, img, index).ConfigureAwait(false);
                     }
                     catch (ArgumentException)
                     {
@@ -1913,7 +1914,7 @@ namespace Emby.Server.Implementations.Library
                 }
                 catch (Exception ex)
                 {
-                    _logger.LogError(ex, "Cannnot get image dimensions for {0}", image.Path);
+                    _logger.LogError(ex, "Cannot get image dimensions for {0}", image.Path);
                     image.Width = 0;
                     image.Height = 0;
                     continue;
@@ -1943,10 +1944,8 @@ namespace Emby.Server.Implementations.Library
             RegisterItem(item);
         }
 
-        /// <summary>
-        /// Updates the item.
-        /// </summary>
-        public void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+        /// <inheritdoc />
+        public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
         {
             foreach (var item in items)
             {
@@ -1957,7 +1956,7 @@ namespace Emby.Server.Implementations.Library
 
                 item.DateLastSaved = DateTime.UtcNow;
 
-                UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
+                await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
             }
 
             _itemRepository.SaveItems(items, cancellationToken);
@@ -1991,17 +1990,9 @@ namespace Emby.Server.Implementations.Library
             }
         }
 
-        /// <summary>
-        /// Updates the item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="parent">The parent item.</param>
-        /// <param name="updateReason">The update reason.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
-        {
-            UpdateItems(new[] { item }, parent, updateReason, cancellationToken);
-        }
+        /// <inheritdoc />
+        public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+            => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
 
         /// <summary>
         /// Reports the item removed.
@@ -2233,7 +2224,7 @@ namespace Emby.Server.Implementations.Library
 
             if (refresh)
             {
-                item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
+                item.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
                 ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
             }
 
@@ -2420,7 +2411,7 @@ namespace Emby.Server.Implementations.Library
             if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
             {
                 item.ViewType = viewType;
-                item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
             }
 
             var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
@@ -2902,7 +2893,7 @@ namespace Emby.Server.Implementations.Library
 
                     await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
 
-                    item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+                    await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
 
                     return item.GetImageInfo(image.Type, imageIndex);
                 }
@@ -2920,7 +2911,7 @@ namespace Emby.Server.Implementations.Library
 
             // Remove this image to prevent it from retrying over and over
             item.RemoveImage(image);
-            item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+            await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
 
             throw new InvalidOperationException();
         }

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

@@ -13,6 +13,7 @@ using System.Threading.Tasks;
 using System.Xml;
 using Emby.Server.Implementations.Library;
 using Jellyfin.Data.Enums;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
@@ -29,7 +30,6 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.MediaInfo;

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

@@ -5,8 +5,8 @@ using System.Collections.Concurrent;
 using System.Globalization;
 using System.Linq;
 using System.Threading;
+using Jellyfin.Data.Events;
 using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Serialization;
 using Microsoft.Extensions.Logging;

+ 11 - 7
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -10,6 +10,7 @@ using System.Threading.Tasks;
 using Emby.Server.Implementations.Library;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Progress;
@@ -24,7 +25,6 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.LiveTv;
@@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.LiveTv
             }
         }
 
-        private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
+        private async Task<LiveTvChannel> GetChannelAsync(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
         {
             var parentFolderId = parentFolder.Id;
             var isNew = false;
@@ -512,7 +512,7 @@ namespace Emby.Server.Implementations.LiveTv
             }
             else if (forceUpdate)
             {
-                _libraryManager.UpdateItem(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken);
+                await _libraryManager.UpdateItemAsync(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
             }
 
             return item;
@@ -1129,7 +1129,7 @@ namespace Emby.Server.Implementations.LiveTv
 
                 try
                 {
-                    var item = GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken);
+                    var item = await GetChannelAsync(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken).ConfigureAwait(false);
 
                     list.Add(item);
                 }
@@ -1146,7 +1146,7 @@ namespace Emby.Server.Implementations.LiveTv
                 double percent = numComplete;
                 percent /= allChannelsList.Count;
 
-                progress.Report(5 * percent + 10);
+                progress.Report((5 * percent) + 10);
             }
 
             progress.Report(15);
@@ -1221,7 +1221,11 @@ namespace Emby.Server.Implementations.LiveTv
 
                     if (updatedPrograms.Count > 0)
                     {
-                        _libraryManager.UpdateItems(updatedPrograms, currentChannel, ItemUpdateType.MetadataImport, cancellationToken);
+                        await _libraryManager.UpdateItemsAsync(
+                            updatedPrograms,
+                            currentChannel,
+                            ItemUpdateType.MetadataImport,
+                            cancellationToken).ConfigureAwait(false);
                     }
 
                     currentChannel.IsMovie = isMovie;
@@ -1234,7 +1238,7 @@ namespace Emby.Server.Implementations.LiveTv
                         currentChannel.AddTag("Kids");
                     }
 
-                    currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
+                    await currentChannel.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
                     await currentChannel.RefreshMetadata(
                         new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                         {

+ 2 - 2
Emby.Server.Implementations/Localization/Core/id.json

@@ -1,7 +1,7 @@
 {
     "Albums": "Album",
     "AuthenticationSucceededWithUserName": "{0} berhasil diautentikasi",
-    "AppDeviceValues": "Aplikasi: {0}, Alat: {1}",
+    "AppDeviceValues": "Aplikasi : {0}, Alat : {1}",
     "LabelRunningTimeValue": "Waktu berjalan: {0}",
     "MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
     "MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
@@ -22,7 +22,7 @@
     "HeaderContinueWatching": "Lanjutkan Menonton",
     "HeaderCameraUploads": "Unggahan Kamera",
     "HeaderAlbumArtists": "Album Artis",
-    "Genres": "Genre",
+    "Genres": "Aliran",
     "Folders": "Folder",
     "Favorites": "Favorit",
     "Collections": "Koleksi",

+ 4 - 1
Emby.Server.Implementations/Localization/Core/th.json

@@ -69,5 +69,8 @@
     "AppDeviceValues": "App: {0}, อุปกรณ์: {1}",
     "Albums": "อัลบั้ม",
     "ScheduledTaskStartedWithName": "{0} เริ่มต้น",
-    "ScheduledTaskFailedWithName": "{0} ล้มเหลว"
+    "ScheduledTaskFailedWithName": "{0} ล้มเหลว",
+    "Songs": "เพลง",
+    "Shows": "แสดง",
+    "ServerNameNeedsToBeRestarted": "{0} ต้องการรีสตาร์ท"
 }

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

@@ -152,10 +152,10 @@ namespace Emby.Server.Implementations.Playlists
 
                 if (options.ItemIdList.Length > 0)
                 {
-                    AddToPlaylistInternal(playlist.Id.ToString("N", CultureInfo.InvariantCulture), options.ItemIdList, user, new DtoOptions(false)
+                    await AddToPlaylistInternal(playlist.Id, options.ItemIdList, user, new DtoOptions(false)
                     {
                         EnableImages = true
-                    });
+                    }).ConfigureAwait(false);
                 }
 
                 return new PlaylistCreationResult(playlist.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -184,17 +184,17 @@ namespace Emby.Server.Implementations.Playlists
             return Playlist.GetPlaylistItems(playlistMediaType, items, user, options);
         }
 
-        public void AddToPlaylist(string playlistId, ICollection<Guid> itemIds, Guid userId)
+        public Task AddToPlaylistAsync(Guid playlistId, ICollection<Guid> itemIds, Guid userId)
         {
             var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId);
 
-            AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
+            return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
             {
                 EnableImages = true
             });
         }
 
-        private void AddToPlaylistInternal(string playlistId, ICollection<Guid> newItemIds, User user, DtoOptions options)
+        private async Task AddToPlaylistInternal(Guid playlistId, ICollection<Guid> newItemIds, User user, DtoOptions options)
         {
             // Retrieve the existing playlist
             var playlist = _libraryManager.GetItemById(playlistId) as Playlist
@@ -238,7 +238,7 @@ namespace Emby.Server.Implementations.Playlists
 
             // Update the playlist in the repository
             playlist.LinkedChildren = newLinkedChildren;
-            playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             // Update the playlist on disk
             if (playlist.IsFile)
@@ -256,7 +256,7 @@ namespace Emby.Server.Implementations.Playlists
                 RefreshPriority.High);
         }
 
-        public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
+        public async Task RemoveFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds)
         {
             if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
             {
@@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.Playlists
                 .Select(i => i.Item1)
                 .ToArray();
 
-            playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             if (playlist.IsFile)
             {
@@ -289,7 +289,7 @@ namespace Emby.Server.Implementations.Playlists
                 RefreshPriority.High);
         }
 
-        public void MoveItem(string playlistId, string entryId, int newIndex)
+        public async Task MoveItemAsync(string playlistId, string entryId, int newIndex)
         {
             if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
             {
@@ -322,7 +322,7 @@ namespace Emby.Server.Implementations.Playlists
 
             playlist.LinkedChildren = newList.ToArray();
 
-            playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             if (playlist.IsFile)
             {

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

@@ -6,10 +6,10 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Progress;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/TaskManager.cs

@@ -5,8 +5,8 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;

+ 61 - 56
Emby.Server.Implementations/Session/SessionManager.cs

@@ -9,6 +9,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
+using Jellyfin.Data.Events;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller;
@@ -17,6 +18,8 @@ using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Events.Session;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Security;
@@ -24,7 +27,6 @@ using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Session;
@@ -40,25 +42,16 @@ namespace Emby.Server.Implementations.Session
     /// </summary>
     public class SessionManager : ISessionManager, IDisposable
     {
-        /// <summary>
-        /// The user data repository.
-        /// </summary>
         private readonly IUserDataManager _userDataManager;
-
-        /// <summary>
-        /// The logger.
-        /// </summary>
         private readonly ILogger<SessionManager> _logger;
-
+        private readonly IEventManager _eventManager;
         private readonly ILibraryManager _libraryManager;
         private readonly IUserManager _userManager;
         private readonly IMusicManager _musicManager;
         private readonly IDtoService _dtoService;
         private readonly IImageProcessor _imageProcessor;
         private readonly IMediaSourceManager _mediaSourceManager;
-
         private readonly IServerApplicationHost _appHost;
-
         private readonly IAuthenticationRepository _authRepo;
         private readonly IDeviceManager _deviceManager;
 
@@ -75,6 +68,7 @@ namespace Emby.Server.Implementations.Session
 
         public SessionManager(
             ILogger<SessionManager> logger,
+            IEventManager eventManager,
             IUserDataManager userDataManager,
             ILibraryManager libraryManager,
             IUserManager userManager,
@@ -87,6 +81,7 @@ namespace Emby.Server.Implementations.Session
             IMediaSourceManager mediaSourceManager)
         {
             _logger = logger;
+            _eventManager = eventManager;
             _userDataManager = userDataManager;
             _libraryManager = libraryManager;
             _userManager = userManager;
@@ -209,6 +204,8 @@ namespace Emby.Server.Implementations.Session
                 }
             }
 
+            _eventManager.Publish(new SessionStartedEventArgs(info));
+
             EventHelper.QueueEventIfNotNull(
                 SessionStarted,
                 this,
@@ -230,6 +227,8 @@ namespace Emby.Server.Implementations.Session
                 },
                 _logger);
 
+            _eventManager.Publish(new SessionEndedEventArgs(info));
+
             info.Dispose();
         }
 
@@ -667,22 +666,26 @@ namespace Emby.Server.Implementations.Session
                 }
             }
 
+            var eventArgs = new PlaybackProgressEventArgs
+            {
+                Item = libraryItem,
+                Users = users,
+                MediaSourceId = info.MediaSourceId,
+                MediaInfo = info.Item,
+                DeviceName = session.DeviceName,
+                ClientName = session.Client,
+                DeviceId = session.DeviceId,
+                Session = session
+            };
+
+            await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
+
             // Nothing to save here
             // Fire events to inform plugins
             EventHelper.QueueEventIfNotNull(
                 PlaybackStart,
                 this,
-                new PlaybackProgressEventArgs
-                {
-                    Item = libraryItem,
-                    Users = users,
-                    MediaSourceId = info.MediaSourceId,
-                    MediaInfo = info.Item,
-                    DeviceName = session.DeviceName,
-                    ClientName = session.Client,
-                    DeviceId = session.DeviceId,
-                    Session = session
-                },
+                eventArgs,
                 _logger);
 
             StartIdleCheckTimer();
@@ -750,23 +753,25 @@ namespace Emby.Server.Implementations.Session
                 }
             }
 
-            PlaybackProgress?.Invoke(
-                this,
-                new PlaybackProgressEventArgs
-                {
-                    Item = libraryItem,
-                    Users = users,
-                    PlaybackPositionTicks = session.PlayState.PositionTicks,
-                    MediaSourceId = session.PlayState.MediaSourceId,
-                    MediaInfo = info.Item,
-                    DeviceName = session.DeviceName,
-                    ClientName = session.Client,
-                    DeviceId = session.DeviceId,
-                    IsPaused = info.IsPaused,
-                    PlaySessionId = info.PlaySessionId,
-                    IsAutomated = isAutomated,
-                    Session = session
-                });
+            var eventArgs = new PlaybackProgressEventArgs
+            {
+                Item = libraryItem,
+                Users = users,
+                PlaybackPositionTicks = session.PlayState.PositionTicks,
+                MediaSourceId = session.PlayState.MediaSourceId,
+                MediaInfo = info.Item,
+                DeviceName = session.DeviceName,
+                ClientName = session.Client,
+                DeviceId = session.DeviceId,
+                IsPaused = info.IsPaused,
+                PlaySessionId = info.PlaySessionId,
+                IsAutomated = isAutomated,
+                Session = session
+            };
+
+            await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
+
+            PlaybackProgress?.Invoke(this, eventArgs);
 
             if (!isAutomated)
             {
@@ -943,23 +948,23 @@ namespace Emby.Server.Implementations.Session
                 }
             }
 
-            EventHelper.QueueEventIfNotNull(
-                PlaybackStopped,
-                this,
-                new PlaybackStopEventArgs
-                {
-                    Item = libraryItem,
-                    Users = users,
-                    PlaybackPositionTicks = info.PositionTicks,
-                    PlayedToCompletion = playedToCompletion,
-                    MediaSourceId = info.MediaSourceId,
-                    MediaInfo = info.Item,
-                    DeviceName = session.DeviceName,
-                    ClientName = session.Client,
-                    DeviceId = session.DeviceId,
-                    Session = session
-                },
-                _logger);
+            var eventArgs = new PlaybackStopEventArgs
+            {
+                Item = libraryItem,
+                Users = users,
+                PlaybackPositionTicks = info.PositionTicks,
+                PlayedToCompletion = playedToCompletion,
+                MediaSourceId = info.MediaSourceId,
+                MediaInfo = info.Item,
+                DeviceName = session.DeviceName,
+                ClientName = session.Client,
+                DeviceId = session.DeviceId,
+                Session = session
+            };
+
+            await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
+
+            EventHelper.QueueEventIfNotNull(PlaybackStopped, this, eventArgs, _logger);
         }
 
         private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed)

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

@@ -4,9 +4,9 @@ using System.Linq;
 using System.Net.WebSockets;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Data.Events;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Net;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;

+ 8 - 7
Jellyfin.Api/Controllers/CollectionController.cs

@@ -1,5 +1,6 @@
 using System;
 using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
@@ -51,7 +52,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="CollectionCreationOptions"/> with information about the new collection.</returns>
         [HttpPost]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<CollectionCreationResult> CreateCollection(
+        public async Task<ActionResult<CollectionCreationResult>> CreateCollection(
             [FromQuery] string? name,
             [FromQuery] string? ids,
             [FromQuery] Guid? parentId,
@@ -59,14 +60,14 @@ namespace Jellyfin.Api.Controllers
         {
             var userId = _authContext.GetAuthorizationInfo(Request).UserId;
 
-            var item = _collectionManager.CreateCollection(new CollectionCreationOptions
+            var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
             {
                 IsLocked = isLocked,
                 Name = name,
                 ParentId = parentId,
                 ItemIdList = RequestHelpers.Split(ids, ',', true),
                 UserIds = new[] { userId }
-            });
+            }).ConfigureAwait(false);
 
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
@@ -87,9 +88,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("{collectionId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+        public async Task<ActionResult> AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
         {
-            _collectionManager.AddToCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
+            await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
             return NoContent();
         }
 
@@ -102,9 +103,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpDelete("{collectionId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+        public async Task<ActionResult> RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
         {
-            _collectionManager.RemoveFromCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
+            await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
             return NoContent();
         }
     }

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

@@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("MediaEncoder/Path")]
         [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult UpdateMediaEncoderPath([FromForm, Required] MediaEncoderPathDto mediaEncoderPath)
+        public ActionResult UpdateMediaEncoderPath([FromBody, Required] MediaEncoderPathDto mediaEncoderPath)
         {
             _mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
             return NoContent();

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

@@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers
             });
         }
 
-        private EventSubscriptionResponse ProcessEventRequest(IEventManager eventManager)
+        private EventSubscriptionResponse ProcessEventRequest(IDlnaEventManager dlnaEventManager)
         {
             var subscriptionId = Request.Headers["SID"];
             if (string.Equals(Request.Method, "subscribe", StringComparison.OrdinalIgnoreCase))
@@ -240,17 +240,17 @@ namespace Jellyfin.Api.Controllers
 
                 if (string.IsNullOrEmpty(notificationType))
                 {
-                    return eventManager.RenewEventSubscription(
+                    return dlnaEventManager.RenewEventSubscription(
                         subscriptionId,
                         notificationType,
                         timeoutString,
                         callback);
                 }
 
-                return eventManager.CreateEventSubscription(notificationType, timeoutString, callback);
+                return dlnaEventManager.CreateEventSubscription(notificationType, timeoutString, callback);
             }
 
-            return eventManager.CancelEventSubscription(subscriptionId);
+            return dlnaEventManager.CancelEventSubscription(subscriptionId);
         }
     }
 }

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

@@ -1356,7 +1356,7 @@ namespace Jellyfin.Api.Controllers
 
             return string.Format(
                 CultureInfo.InvariantCulture,
-                "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
+                "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
                 inputModifier,
                 _encodingHelper.GetInputArgument(state, encodingOptions),
                 threads,

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

@@ -174,7 +174,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteItemImage(
+        public async Task<ActionResult> DeleteItemImage(
             [FromRoute] Guid itemId,
             [FromRoute] ImageType imageType,
             [FromRoute] int? imageIndex = null)
@@ -185,7 +185,7 @@ namespace Jellyfin.Api.Controllers
                 return NotFound();
             }
 
-            item.DeleteImage(imageType, imageIndex ?? 0);
+            await item.DeleteImageAsync(imageType, imageIndex ?? 0).ConfigureAwait(false);
             return NoContent();
         }
 
@@ -218,7 +218,7 @@ namespace Jellyfin.Api.Controllers
             // Handle image/png; charset=utf-8
             var mimeType = Request.ContentType.Split(';').FirstOrDefault();
             await _providerManager.SaveImage(item, Request.Body, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
-            item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+            await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
 
             return NoContent();
         }
@@ -237,7 +237,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult UpdateItemImageIndex(
+        public async Task<ActionResult> UpdateItemImageIndex(
             [FromRoute] Guid itemId,
             [FromRoute] ImageType imageType,
             [FromRoute] int imageIndex,
@@ -249,7 +249,7 @@ namespace Jellyfin.Api.Controllers
                 return NotFound();
             }
 
-            item.SwapImages(imageType, imageIndex, newIndex);
+            await item.SwapImagesAsync(imageType, imageIndex, newIndex).ConfigureAwait(false);
             return NoContent();
         }
 
@@ -264,7 +264,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<IEnumerable<ImageInfo>> GetItemImageInfos([FromRoute] Guid itemId)
+        public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute] Guid itemId)
         {
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
@@ -281,7 +281,7 @@ namespace Jellyfin.Api.Controllers
                 return list;
             }
 
-            _libraryManager.UpdateImages(item); // this makes sure dimensions and hashes are correct
+            await _libraryManager.UpdateImagesAsync(item).ConfigureAwait(false); // this makes sure dimensions and hashes are correct
 
             foreach (var image in itemImages)
             {
@@ -1281,9 +1281,9 @@ namespace Jellyfin.Api.Controllers
                 Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false)));
 
                 // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified
-                if (!(dateImageModified > ifModifiedSinceHeader))
+                if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue)
                 {
-                    if (ifModifiedSinceHeader.Add(cacheDuration!.Value) < DateTime.UtcNow)
+                    if (ifModifiedSinceHeader.Add(cacheDuration.Value) < DateTime.UtcNow)
                     {
                         Response.StatusCode = StatusCodes.Status304NotModified;
                         return new ContentResult();

+ 4 - 3
Jellyfin.Api/Controllers/ItemUpdateController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Threading;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -67,7 +68,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/{itemId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request)
+        public async Task<ActionResult> UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request)
         {
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
@@ -101,7 +102,7 @@ namespace Jellyfin.Api.Controllers
 
             item.OnMetadataChanged();
 
-            item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             if (isLockedChanged && item.IsFolder)
             {
@@ -110,7 +111,7 @@ namespace Jellyfin.Api.Controllers
                 foreach (var child in folder.GetRecursiveChildren())
                 {
                     child.IsLocked = newLockData;
-                    child.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                    await child.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
                 }
             }
 

+ 4 - 2
Jellyfin.Api/Controllers/ItemsController.cs

@@ -4,10 +4,10 @@ using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
-using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -266,7 +266,9 @@ namespace Jellyfin.Api.Controllers
 
             bool isInEnabledFolder = user!.GetPreference(PreferenceKind.EnabledFolders).Any(i => new Guid(i) == item.Id)
                                      // Assume all folders inside an EnabledChannel are enabled
-                                     || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.Id);
+                                     || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.Id)
+                                     // Assume all items inside an EnabledChannel are enabled
+                                     || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.ChannelId);
 
             var collectionFolders = _libraryManager.GetCollectionFolders(item);
             foreach (var collectionFolder in collectionFolders)

+ 6 - 7
Jellyfin.Api/Controllers/LibraryController.cs

@@ -19,6 +19,7 @@ using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Net;
@@ -35,8 +36,6 @@ using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Logging;
 using Book = MediaBrowser.Controller.Entities.Book;
-using Movie = Jellyfin.Data.Entities.Movie;
-using MusicAlbum = Jellyfin.Data.Entities.MusicAlbum;
 
 namespace Jellyfin.Api.Controllers
 {
@@ -619,7 +618,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.Download)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult GetDownload([FromRoute] Guid itemId)
+        public async Task<ActionResult> GetDownload([FromRoute] Guid itemId)
         {
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
@@ -648,7 +647,7 @@ namespace Jellyfin.Api.Controllers
 
             if (user != null)
             {
-                LogDownload(item, user, auth);
+                await LogDownloadAsync(item, user, auth).ConfigureAwait(false);
             }
 
             var path = item.Path;
@@ -861,17 +860,17 @@ namespace Jellyfin.Api.Controllers
                 : item;
         }
 
-        private void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
+        private async Task LogDownloadAsync(BaseItem item, User user, AuthorizationInfo auth)
         {
             try
             {
-                _activityManager.Create(new ActivityLog(
+                await _activityManager.CreateAsync(new ActivityLog(
                     string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name),
                     "UserDownloadingContent",
                     auth.UserId)
                 {
                     ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
-                });
+                }).ConfigureAwait(false);
             }
             catch
             {

+ 5 - 7
Jellyfin.Api/Controllers/LibraryStructureController.cs

@@ -76,7 +76,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? name,
             [FromQuery] string? collectionType,
             [FromQuery] string[] paths,
-            [FromBody] LibraryOptionsDto? libraryOptionsDto,
+            [FromBody] AddVirtualFolderDto? libraryOptionsDto,
             [FromQuery] bool refreshLibrary = false)
         {
             var libraryOptions = libraryOptionsDto?.LibraryOptions ?? new LibraryOptions();
@@ -312,19 +312,17 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Update library options.
         /// </summary>
-        /// <param name="id">The library name.</param>
-        /// <param name="libraryOptions">The library options.</param>
+        /// <param name="request">The library name and options.</param>
         /// <response code="204">Library updated.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpPost("LibraryOptions")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult UpdateLibraryOptions(
-            [FromQuery] string? id,
-            [FromBody] LibraryOptions? libraryOptions)
+            [FromBody] UpdateLibraryOptionsDto request)
         {
-            var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(id);
+            var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(request.Id);
 
-            collectionFolder.UpdateLibraryOptions(libraryOptions);
+            collectionFolder.UpdateLibraryOptions(request.LibraryOptions);
             return NoContent();
         }
     }

+ 2 - 2
Jellyfin.Api/Controllers/MediaInfoController.cs

@@ -269,9 +269,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("LiveStreams/Close")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult CloseLiveStream([FromQuery, Required] string? liveStreamId)
+        public async Task<ActionResult> CloseLiveStream([FromQuery, Required] string? liveStreamId)
         {
-            _mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult();
+            await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
             return NoContent();
         }
 

+ 2 - 1
Jellyfin.Api/Controllers/MoviesController.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.Dto;
@@ -181,7 +182,7 @@ namespace Jellyfin.Api.Controllers
             DtoOptions dtoOptions,
             RecommendationType type)
         {
-            var itemTypes = new List<string> { nameof(MediaBrowser.Controller.Entities.Movies.Movie) };
+            var itemTypes = new List<string> { nameof(Movie) };
             if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
             {
                 itemTypes.Add(nameof(Trailer));

+ 7 - 7
Jellyfin.Api/Controllers/PlaylistsController.cs

@@ -83,12 +83,12 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         [HttpPost("{playlistId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult AddToPlaylist(
-            [FromRoute] string? playlistId,
+        public async Task<ActionResult> AddToPlaylist(
+            [FromRoute] Guid playlistId,
             [FromQuery] string? ids,
             [FromQuery] Guid? userId)
         {
-            _playlistManager.AddToPlaylist(playlistId, RequestHelpers.GetGuids(ids), userId ?? Guid.Empty);
+            await _playlistManager.AddToPlaylistAsync(playlistId, RequestHelpers.GetGuids(ids), userId ?? Guid.Empty).ConfigureAwait(false);
             return NoContent();
         }
 
@@ -102,12 +102,12 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult MoveItem(
+        public async Task<ActionResult> MoveItem(
             [FromRoute] string? playlistId,
             [FromRoute] string? itemId,
             [FromRoute] int newIndex)
         {
-            _playlistManager.MoveItem(playlistId, itemId, newIndex);
+            await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
             return NoContent();
         }
 
@@ -120,9 +120,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         [HttpDelete("{playlistId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds)
+        public async Task<ActionResult> RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds)
         {
-            _playlistManager.RemoveFromPlaylist(playlistId, RequestHelpers.Split(entryIds, ',', true));
+            await _playlistManager.RemoveFromPlaylistAsync(playlistId, RequestHelpers.Split(entryIds, ',', true)).ConfigureAwait(false);
             return NoContent();
         }
 

+ 6 - 2
Jellyfin.Api/Controllers/PluginsController.cs

@@ -120,10 +120,14 @@ namespace Jellyfin.Api.Controllers
                 return NotFound();
             }
 
-            var configuration = (BasePluginConfiguration)await JsonSerializer.DeserializeAsync(Request.Body, plugin.ConfigurationType, _serializerOptions)
+            var configuration = (BasePluginConfiguration?)await JsonSerializer.DeserializeAsync(Request.Body, plugin.ConfigurationType, _serializerOptions)
                 .ConfigureAwait(false);
 
-            plugin.UpdateConfiguration(configuration);
+            if (configuration != null)
+            {
+                plugin.UpdateConfiguration(configuration);
+            }
+
             return NoContent();
         }
 

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

@@ -221,7 +221,7 @@ namespace Jellyfin.Api.Controllers
             await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None)
                 .ConfigureAwait(false);
 
-            item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+            await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
             return NoContent();
         }
 

+ 6 - 10
Jellyfin.Api/Controllers/SessionController.cs

@@ -153,7 +153,6 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemIds">The ids of the items to play, comma delimited.</param>
         /// <param name="startPositionTicks">The starting position of the first item.</param>
         /// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
-        /// <param name="playRequest">The <see cref="PlayRequest"/>.</param>
         /// <response code="204">Instruction sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpPost("Sessions/{sessionId}/Playing")]
@@ -163,17 +162,14 @@ namespace Jellyfin.Api.Controllers
             [FromRoute, Required] string? sessionId,
             [FromQuery] Guid[] itemIds,
             [FromQuery] long? startPositionTicks,
-            [FromQuery] PlayCommand playCommand,
-            [FromBody, Required] PlayRequest playRequest)
+            [FromQuery] PlayCommand playCommand)
         {
-            if (playRequest == null)
+            var playRequest = new PlayRequest
             {
-                throw new ArgumentException("Request Body may not be null");
-            }
-
-            playRequest.ItemIds = itemIds;
-            playRequest.StartPositionTicks = startPositionTicks;
-            playRequest.PlayCommand = playCommand;
+                ItemIds = itemIds,
+                StartPositionTicks = startPositionTicks,
+                PlayCommand = playCommand
+            };
 
             _sessionManager.SendPlayCommand(
                 RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id,

+ 11 - 16
Jellyfin.Api/Controllers/StartupController.cs

@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
@@ -64,21 +65,16 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Sets the initial startup wizard configuration.
         /// </summary>
-        /// <param name="uiCulture">The UI language culture.</param>
-        /// <param name="metadataCountryCode">The metadata country code.</param>
-        /// <param name="preferredMetadataLanguage">The preferred language for metadata.</param>
+        /// <param name="startupConfiguration">The updated startup configuration.</param>
         /// <response code="204">Configuration saved.</response>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Configuration")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult UpdateInitialConfiguration(
-            [FromForm] string? uiCulture,
-            [FromForm] string? metadataCountryCode,
-            [FromForm] string? preferredMetadataLanguage)
+        public ActionResult UpdateInitialConfiguration([FromBody, Required] StartupConfigurationDto startupConfiguration)
         {
-            _config.Configuration.UICulture = uiCulture;
-            _config.Configuration.MetadataCountryCode = metadataCountryCode;
-            _config.Configuration.PreferredMetadataLanguage = preferredMetadataLanguage;
+            _config.Configuration.UICulture = startupConfiguration.UICulture;
+            _config.Configuration.MetadataCountryCode = startupConfiguration.MetadataCountryCode;
+            _config.Configuration.PreferredMetadataLanguage = startupConfiguration.PreferredMetadataLanguage;
             _config.SaveConfiguration();
             return NoContent();
         }
@@ -86,16 +82,15 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Sets remote access and UPnP.
         /// </summary>
-        /// <param name="enableRemoteAccess">Enable remote access.</param>
-        /// <param name="enableAutomaticPortMapping">Enable UPnP.</param>
+        /// <param name="startupRemoteAccessDto">The startup remote access dto.</param>
         /// <response code="204">Configuration saved.</response>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("RemoteAccess")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping)
+        public ActionResult SetRemoteAccess([FromBody, Required] StartupRemoteAccessDto startupRemoteAccessDto)
         {
-            _config.Configuration.EnableRemoteAccess = enableRemoteAccess;
-            _config.Configuration.EnableUPnP = enableAutomaticPortMapping;
+            _config.Configuration.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess;
+            _config.Configuration.EnableUPnP = startupRemoteAccessDto.EnableAutomaticPortMapping;
             _config.SaveConfiguration();
             return NoContent();
         }
@@ -131,7 +126,7 @@ namespace Jellyfin.Api.Controllers
         /// </returns>
         [HttpPost("User")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> UpdateStartupUser([FromForm] StartupUserDto startupUserDto)
+        public async Task<ActionResult> UpdateStartupUser([FromBody] StartupUserDto startupUserDto)
         {
             var user = _userManager.Users.First();
 

+ 7 - 7
Jellyfin.Api/Controllers/VideosController.cs

@@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteAlternateSources([FromRoute] Guid itemId)
+        public async Task<ActionResult> DeleteAlternateSources([FromRoute] Guid itemId)
         {
             var video = (Video)_libraryManager.GetItemById(itemId);
 
@@ -180,12 +180,12 @@ namespace Jellyfin.Api.Controllers
                 link.SetPrimaryVersionId(null);
                 link.LinkedAlternateVersions = Array.Empty<LinkedChild>();
 
-                link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                await link.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             }
 
             video.LinkedAlternateVersions = Array.Empty<LinkedChild>();
             video.SetPrimaryVersionId(null);
-            video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await video.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             return NoContent();
         }
@@ -201,7 +201,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
-        public ActionResult MergeVersions([FromQuery, Required] string? itemIds)
+        public async Task<ActionResult> MergeVersions([FromQuery, Required] string? itemIds)
         {
             var items = RequestHelpers.Split(itemIds, ',', true)
                 .Select(i => _libraryManager.GetItemById(i))
@@ -239,7 +239,7 @@ namespace Jellyfin.Api.Controllers
             {
                 item.SetPrimaryVersionId(primaryVersion.Id.ToString("N", CultureInfo.InvariantCulture));
 
-                item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
                 list.Add(new LinkedChild
                 {
@@ -258,12 +258,12 @@ namespace Jellyfin.Api.Controllers
                 if (item.LinkedAlternateVersions.Length > 0)
                 {
                     item.LinkedAlternateVersions = Array.Empty<LinkedChild>();
-                    item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                    await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
                 }
             }
 
             primaryVersion.LinkedAlternateVersions = list.ToArray();
-            primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await primaryVersion.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             return NoContent();
         }
 

+ 5 - 1
Jellyfin.Api/Helpers/MediaInfoHelper.cs

@@ -127,7 +127,11 @@ namespace Jellyfin.Api.Helpers
             {
                 // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
                 // Should we move this directly into MediaSourceManager?
-                result.MediaSources = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
+                var mediaSourcesClone = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
+                if (mediaSourcesClone != null)
+                {
+                    result.MediaSources = mediaSourcesClone;
+                }
 
                 result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
             }

+ 3 - 3
Jellyfin.Api/Models/LibraryStructureDto/LibraryOptionsDto.cs → Jellyfin.Api/Models/LibraryStructureDto/AddVirtualFolderDto.cs

@@ -3,13 +3,13 @@
 namespace Jellyfin.Api.Models.LibraryStructureDto
 {
     /// <summary>
-    /// Library options dto.
+    /// Add virtual folder dto.
     /// </summary>
-    public class LibraryOptionsDto
+    public class AddVirtualFolderDto
     {
         /// <summary>
         /// Gets or sets library options.
         /// </summary>
         public LibraryOptions? LibraryOptions { get; set; }
     }
-}
+}

+ 21 - 0
Jellyfin.Api/Models/LibraryStructureDto/UpdateLibraryOptionsDto.cs

@@ -0,0 +1,21 @@
+using System;
+using MediaBrowser.Model.Configuration;
+
+namespace Jellyfin.Api.Models.LibraryStructureDto
+{
+    /// <summary>
+    /// Update library options dto.
+    /// </summary>
+    public class UpdateLibraryOptionsDto
+    {
+        /// <summary>
+        /// Gets or sets the library item id.
+        /// </summary>
+        public Guid Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets library options.
+        /// </summary>
+        public LibraryOptions? LibraryOptions { get; set; }
+    }
+}

+ 22 - 0
Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs

@@ -0,0 +1,22 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Jellyfin.Api.Models.StartupDtos
+{
+    /// <summary>
+    /// Startup remote access dto.
+    /// </summary>
+    public class StartupRemoteAccessDto
+    {
+        /// <summary>
+        /// Gets or sets a value indicating whether enable remote access.
+        /// </summary>
+        [Required]
+        public bool EnableRemoteAccess { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether enable automatic port mapping.
+        /// </summary>
+        [Required]
+        public bool EnableAutomaticPortMapping { get; set; }
+    }
+}

+ 1 - 1
Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs

@@ -1,8 +1,8 @@
 using System;
 using System.Threading.Tasks;
+using Jellyfin.Data.Events;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Activity;
-using MediaBrowser.Model.Events;
 using Microsoft.Extensions.Logging;
 
 namespace Jellyfin.Api.WebSocketListeners

+ 1 - 1
Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs

@@ -1,8 +1,8 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
+using Jellyfin.Data.Events;
 using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 

+ 2 - 0
Jellyfin.Data/DayOfWeekHelper.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System;
 using System.Collections.Generic;
 using Jellyfin.Data.Enums;

+ 4 - 1
Jellyfin.Data/Entities/ActivityLog.cs

@@ -1,6 +1,9 @@
+#pragma warning disable CS1591
+
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
 using Microsoft.Extensions.Logging;
 
 namespace Jellyfin.Data.Entities
@@ -8,7 +11,7 @@ namespace Jellyfin.Data.Entities
     /// <summary>
     /// An entity referencing an activity log entry.
     /// </summary>
-    public partial class ActivityLog : ISavingChanges
+    public partial class ActivityLog : IHasConcurrencyToken
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="ActivityLog"/> class.

+ 0 - 208
Jellyfin.Data/Entities/Artwork.cs

@@ -1,208 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class Artwork
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected Artwork()
-        {
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static Artwork CreateArtworkUnsafe()
-        {
-            return new Artwork();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="path"></param>
-        /// <param name="kind"></param>
-        /// <param name="_metadata0"></param>
-        /// <param name="_personrole1"></param>
-        public Artwork(string path, Enums.ArtKind kind, Metadata _metadata0, PersonRole _personrole1)
-        {
-            if (string.IsNullOrEmpty(path))
-            {
-                throw new ArgumentNullException(nameof(path));
-            }
-
-            this.Path = path;
-
-            this.Kind = kind;
-
-            if (_metadata0 == null)
-            {
-                throw new ArgumentNullException(nameof(_metadata0));
-            }
-
-            _metadata0.Artwork.Add(this);
-
-            if (_personrole1 == null)
-            {
-                throw new ArgumentNullException(nameof(_personrole1));
-            }
-
-            _personrole1.Artwork = this;
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="path"></param>
-        /// <param name="kind"></param>
-        /// <param name="_metadata0"></param>
-        /// <param name="_personrole1"></param>
-        public static Artwork Create(string path, Enums.ArtKind kind, Metadata _metadata0, PersonRole _personrole1)
-        {
-            return new Artwork(path, kind, _metadata0, _personrole1);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for Id.
-        /// </summary>
-        internal int _Id;
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before setting.
-        /// </summary>
-        partial void SetId(int oldValue, ref int newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before returning.
-        /// </summary>
-        partial void GetId(ref int result);
-
-        /// <summary>
-        /// Identity, Indexed, Required.
-        /// </summary>
-        [Key]
-        [Required]
-        public int Id
-        {
-            get
-            {
-                int value = _Id;
-                GetId(ref value);
-                return _Id = value;
-            }
-
-            protected set
-            {
-                int oldValue = _Id;
-                SetId(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Id = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Path.
-        /// </summary>
-        protected string _Path;
-        /// <summary>
-        /// When provided in a partial class, allows value of Path to be changed before setting.
-        /// </summary>
-        partial void SetPath(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Path to be changed before returning.
-        /// </summary>
-        partial void GetPath(ref string result);
-
-        /// <summary>
-        /// Required, Max length = 65535
-        /// </summary>
-        [Required]
-        [MaxLength(65535)]
-        [StringLength(65535)]
-        public string Path
-        {
-            get
-            {
-                string value = _Path;
-                GetPath(ref value);
-                return _Path = value;
-            }
-
-            set
-            {
-                string oldValue = _Path;
-                SetPath(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Path = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Kind.
-        /// </summary>
-        internal Enums.ArtKind _Kind;
-        /// <summary>
-        /// When provided in a partial class, allows value of Kind to be changed before setting.
-        /// </summary>
-        partial void SetKind(Enums.ArtKind oldValue, ref Enums.ArtKind newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Kind to be changed before returning.
-        /// </summary>
-        partial void GetKind(ref Enums.ArtKind result);
-
-        /// <summary>
-        /// Indexed, Required.
-        /// </summary>
-        [Required]
-        public Enums.ArtKind Kind
-        {
-            get
-            {
-                Enums.ArtKind value = _Kind;
-                GetKind(ref value);
-                return _Kind = value;
-            }
-
-            set
-            {
-                Enums.ArtKind oldValue = _Kind;
-                SetKind(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Kind = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Required, ConcurrenyToken.
-        /// </summary>
-        [ConcurrencyCheck]
-        [Required]
-        public uint RowVersion { get; set; }
-
-        public void OnSavingChanges()
-        {
-            RowVersion++;
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-    }
-}
-

+ 0 - 68
Jellyfin.Data/Entities/Book.cs

@@ -1,68 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class Book : LibraryItem
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected Book()
-        {
-            BookMetadata = new HashSet<BookMetadata>();
-            Releases = new HashSet<Release>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static Book CreateBookUnsafe()
-        {
-            return new Book();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
-        public Book(Guid urlid, DateTime dateadded)
-        {
-            this.UrlId = urlid;
-
-            this.BookMetadata = new HashSet<BookMetadata>();
-            this.Releases = new HashSet<Release>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
-        public static Book Create(Guid urlid, DateTime dateadded)
-        {
-            return new Book(urlid, dateadded);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-
-        [ForeignKey("BookMetadata_BookMetadata_Id")]
-        public virtual ICollection<BookMetadata> BookMetadata { get; protected set; }
-
-        [ForeignKey("Release_Releases_Id")]
-        public virtual ICollection<Release> Releases { get; protected set; }
-    }
-}
-

+ 0 - 119
Jellyfin.Data/Entities/BookMetadata.cs

@@ -1,119 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class BookMetadata : Metadata
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected BookMetadata()
-        {
-            Publishers = new HashSet<Company>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static BookMetadata CreateBookMetadataUnsafe()
-        {
-            return new BookMetadata();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="title">The title or name of the object.</param>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="_book0"></param>
-        public BookMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Book _book0)
-        {
-            if (string.IsNullOrEmpty(title))
-            {
-                throw new ArgumentNullException(nameof(title));
-            }
-
-            this.Title = title;
-
-            if (string.IsNullOrEmpty(language))
-            {
-                throw new ArgumentNullException(nameof(language));
-            }
-
-            this.Language = language;
-
-            if (_book0 == null)
-            {
-                throw new ArgumentNullException(nameof(_book0));
-            }
-
-            _book0.BookMetadata.Add(this);
-
-            this.Publishers = new HashSet<Company>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="title">The title or name of the object.</param>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="_book0"></param>
-        public static BookMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Book _book0)
-        {
-            return new BookMetadata(title, language, dateadded, datemodified, _book0);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for ISBN.
-        /// </summary>
-        protected long? _ISBN;
-        /// <summary>
-        /// When provided in a partial class, allows value of ISBN to be changed before setting.
-        /// </summary>
-        partial void SetISBN(long? oldValue, ref long? newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of ISBN to be changed before returning.
-        /// </summary>
-        partial void GetISBN(ref long? result);
-
-        public long? ISBN
-        {
-            get
-            {
-                long? value = _ISBN;
-                GetISBN(ref value);
-                return _ISBN = value;
-            }
-
-            set
-            {
-                long? oldValue = _ISBN;
-                SetISBN(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _ISBN = value;
-                }
-            }
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-
-        [ForeignKey("Company_Publishers_Id")]
-        public virtual ICollection<Company> Publishers { get; protected set; }
-    }
-}
-

+ 0 - 275
Jellyfin.Data/Entities/Chapter.cs

@@ -1,275 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class Chapter
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected Chapter()
-        {
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static Chapter CreateChapterUnsafe()
-        {
-            return new Chapter();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="timestart"></param>
-        /// <param name="_release0"></param>
-        public Chapter(string language, long timestart, Release _release0)
-        {
-            if (string.IsNullOrEmpty(language))
-            {
-                throw new ArgumentNullException(nameof(language));
-            }
-
-            this.Language = language;
-
-            this.TimeStart = timestart;
-
-            if (_release0 == null)
-            {
-                throw new ArgumentNullException(nameof(_release0));
-            }
-
-            _release0.Chapters.Add(this);
-
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="timestart"></param>
-        /// <param name="_release0"></param>
-        public static Chapter Create(string language, long timestart, Release _release0)
-        {
-            return new Chapter(language, timestart, _release0);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for Id.
-        /// </summary>
-        internal int _Id;
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before setting.
-        /// </summary>
-        partial void SetId(int oldValue, ref int newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before returning.
-        /// </summary>
-        partial void GetId(ref int result);
-
-        /// <summary>
-        /// Identity, Indexed, Required.
-        /// </summary>
-        [Key]
-        [Required]
-        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
-        public int Id
-        {
-            get
-            {
-                int value = _Id;
-                GetId(ref value);
-                return _Id = value;
-            }
-
-            protected set
-            {
-                int oldValue = _Id;
-                SetId(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Id = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Name.
-        /// </summary>
-        protected string _Name;
-        /// <summary>
-        /// When provided in a partial class, allows value of Name to be changed before setting.
-        /// </summary>
-        partial void SetName(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Name to be changed before returning.
-        /// </summary>
-        partial void GetName(ref string result);
-
-        /// <summary>
-        /// Max length = 1024
-        /// </summary>
-        [MaxLength(1024)]
-        [StringLength(1024)]
-        public string Name
-        {
-            get
-            {
-                string value = _Name;
-                GetName(ref value);
-                return _Name = value;
-            }
-
-            set
-            {
-                string oldValue = _Name;
-                SetName(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Name = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Language.
-        /// </summary>
-        protected string _Language;
-        /// <summary>
-        /// When provided in a partial class, allows value of Language to be changed before setting.
-        /// </summary>
-        partial void SetLanguage(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Language to be changed before returning.
-        /// </summary>
-        partial void GetLanguage(ref string result);
-
-        /// <summary>
-        /// Required, Min length = 3, Max length = 3
-        /// ISO-639-3 3-character language codes.
-        /// </summary>
-        [Required]
-        [MinLength(3)]
-        [MaxLength(3)]
-        [StringLength(3)]
-        public string Language
-        {
-            get
-            {
-                string value = _Language;
-                GetLanguage(ref value);
-                return _Language = value;
-            }
-
-            set
-            {
-                string oldValue = _Language;
-                SetLanguage(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Language = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for TimeStart.
-        /// </summary>
-        protected long _TimeStart;
-        /// <summary>
-        /// When provided in a partial class, allows value of TimeStart to be changed before setting.
-        /// </summary>
-        partial void SetTimeStart(long oldValue, ref long newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of TimeStart to be changed before returning.
-        /// </summary>
-        partial void GetTimeStart(ref long result);
-
-        /// <summary>
-        /// Required.
-        /// </summary>
-        [Required]
-        public long TimeStart
-        {
-            get
-            {
-                long value = _TimeStart;
-                GetTimeStart(ref value);
-                return _TimeStart = value;
-            }
-
-            set
-            {
-                long oldValue = _TimeStart;
-                SetTimeStart(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _TimeStart = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for TimeEnd.
-        /// </summary>
-        protected long? _TimeEnd;
-        /// <summary>
-        /// When provided in a partial class, allows value of TimeEnd to be changed before setting.
-        /// </summary>
-        partial void SetTimeEnd(long? oldValue, ref long? newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of TimeEnd to be changed before returning.
-        /// </summary>
-        partial void GetTimeEnd(ref long? result);
-
-        public long? TimeEnd
-        {
-            get
-            {
-                long? value = _TimeEnd;
-                GetTimeEnd(ref value);
-                return _TimeEnd = value;
-            }
-
-            set
-            {
-                long? oldValue = _TimeEnd;
-                SetTimeEnd(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _TimeEnd = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Required, ConcurrenyToken.
-        /// </summary>
-        [ConcurrencyCheck]
-        [Required]
-        public uint RowVersion { get; set; }
-
-        public void OnSavingChanges()
-        {
-            RowVersion++;
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-    }
-}
-

+ 0 - 121
Jellyfin.Data/Entities/Collection.cs

@@ -1,121 +0,0 @@
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class Collection
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor.
-        /// </summary>
-        public Collection()
-        {
-            CollectionItem = new LinkedList<CollectionItem>();
-
-            Init();
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for Id.
-        /// </summary>
-        internal int _Id;
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before setting.
-        /// </summary>
-        partial void SetId(int oldValue, ref int newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before returning.
-        /// </summary>
-        partial void GetId(ref int result);
-
-        /// <summary>
-        /// Identity, Indexed, Required.
-        /// </summary>
-        [Key]
-        [Required]
-        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
-        public int Id
-        {
-            get
-            {
-                int value = _Id;
-                GetId(ref value);
-                return _Id = value;
-            }
-
-            protected set
-            {
-                int oldValue = _Id;
-                SetId(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Id = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Name.
-        /// </summary>
-        protected string _Name;
-        /// <summary>
-        /// When provided in a partial class, allows value of Name to be changed before setting.
-        /// </summary>
-        partial void SetName(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Name to be changed before returning.
-        /// </summary>
-        partial void GetName(ref string result);
-
-        /// <summary>
-        /// Max length = 1024
-        /// </summary>
-        [MaxLength(1024)]
-        [StringLength(1024)]
-        public string Name
-        {
-            get
-            {
-                string value = _Name;
-                GetName(ref value);
-                return _Name = value;
-            }
-
-            set
-            {
-                string oldValue = _Name;
-                SetName(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Name = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Required, ConcurrenyToken.
-        /// </summary>
-        [ConcurrencyCheck]
-        [Required]
-        public uint RowVersion { get; set; }
-
-        public void OnSavingChanges()
-        {
-            RowVersion++;
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-        [ForeignKey("CollectionItem_CollectionItem_Id")]
-        public virtual ICollection<CollectionItem> CollectionItem { get; protected set; }
-    }
-}
-

+ 0 - 154
Jellyfin.Data/Entities/CollectionItem.cs

@@ -1,154 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class CollectionItem
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected CollectionItem()
-        {
-            // NOTE: This class has one-to-one associations with CollectionItem.
-            // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
-
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static CollectionItem CreateCollectionItemUnsafe()
-        {
-            return new CollectionItem();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="_collection0"></param>
-        /// <param name="_collectionitem1"></param>
-        /// <param name="_collectionitem2"></param>
-        public CollectionItem(Collection _collection0, CollectionItem _collectionitem1, CollectionItem _collectionitem2)
-        {
-            // NOTE: This class has one-to-one associations with CollectionItem.
-            // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
-
-            if (_collection0 == null)
-            {
-                throw new ArgumentNullException(nameof(_collection0));
-            }
-
-            _collection0.CollectionItem.Add(this);
-
-            if (_collectionitem1 == null)
-            {
-                throw new ArgumentNullException(nameof(_collectionitem1));
-            }
-
-            _collectionitem1.Next = this;
-
-            if (_collectionitem2 == null)
-            {
-                throw new ArgumentNullException(nameof(_collectionitem2));
-            }
-
-            _collectionitem2.Previous = this;
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="_collection0"></param>
-        /// <param name="_collectionitem1"></param>
-        /// <param name="_collectionitem2"></param>
-        public static CollectionItem Create(Collection _collection0, CollectionItem _collectionitem1, CollectionItem _collectionitem2)
-        {
-            return new CollectionItem(_collection0, _collectionitem1, _collectionitem2);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for Id.
-        /// </summary>
-        internal int _Id;
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before setting.
-        /// </summary>
-        partial void SetId(int oldValue, ref int newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before returning.
-        /// </summary>
-        partial void GetId(ref int result);
-
-        /// <summary>
-        /// Identity, Indexed, Required.
-        /// </summary>
-        [Key]
-        [Required]
-        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
-        public int Id
-        {
-            get
-            {
-                int value = _Id;
-                GetId(ref value);
-                return _Id = value;
-            }
-
-            protected set
-            {
-                int oldValue = _Id;
-                SetId(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Id = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Required, ConcurrenyToken.
-        /// </summary>
-        [ConcurrencyCheck]
-        [Required]
-        public uint RowVersion { get; set; }
-
-        public void OnSavingChanges()
-        {
-            RowVersion++;
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Required.
-        /// </summary>
-        [ForeignKey("LibraryItem_Id")]
-        public virtual LibraryItem LibraryItem { get; set; }
-
-        /// <remarks>
-        /// TODO check if this properly updated dependant and has the proper principal relationship
-        /// </remarks>
-        [ForeignKey("CollectionItem_Next_Id")]
-        public virtual CollectionItem Next { get; set; }
-
-        /// <remarks>
-        /// TODO check if this properly updated dependant and has the proper principal relationship
-        /// </remarks>
-        [ForeignKey("CollectionItem_Previous_Id")]
-        public virtual CollectionItem Previous { get; set; }
-    }
-}
-

+ 0 - 157
Jellyfin.Data/Entities/Company.cs

@@ -1,157 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class Company
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected Company()
-        {
-            CompanyMetadata = new HashSet<CompanyMetadata>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static Company CreateCompanyUnsafe()
-        {
-            return new Company();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="_moviemetadata0"></param>
-        /// <param name="_seriesmetadata1"></param>
-        /// <param name="_musicalbummetadata2"></param>
-        /// <param name="_bookmetadata3"></param>
-        /// <param name="_company4"></param>
-        public Company(MovieMetadata _moviemetadata0, SeriesMetadata _seriesmetadata1, MusicAlbumMetadata _musicalbummetadata2, BookMetadata _bookmetadata3, Company _company4)
-        {
-            if (_moviemetadata0 == null)
-            {
-                throw new ArgumentNullException(nameof(_moviemetadata0));
-            }
-
-            _moviemetadata0.Studios.Add(this);
-
-            if (_seriesmetadata1 == null)
-            {
-                throw new ArgumentNullException(nameof(_seriesmetadata1));
-            }
-
-            _seriesmetadata1.Networks.Add(this);
-
-            if (_musicalbummetadata2 == null)
-            {
-                throw new ArgumentNullException(nameof(_musicalbummetadata2));
-            }
-
-            _musicalbummetadata2.Labels.Add(this);
-
-            if (_bookmetadata3 == null)
-            {
-                throw new ArgumentNullException(nameof(_bookmetadata3));
-            }
-
-            _bookmetadata3.Publishers.Add(this);
-
-            if (_company4 == null)
-            {
-                throw new ArgumentNullException(nameof(_company4));
-            }
-
-            _company4.Parent = this;
-
-            this.CompanyMetadata = new HashSet<CompanyMetadata>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="_moviemetadata0"></param>
-        /// <param name="_seriesmetadata1"></param>
-        /// <param name="_musicalbummetadata2"></param>
-        /// <param name="_bookmetadata3"></param>
-        /// <param name="_company4"></param>
-        public static Company Create(MovieMetadata _moviemetadata0, SeriesMetadata _seriesmetadata1, MusicAlbumMetadata _musicalbummetadata2, BookMetadata _bookmetadata3, Company _company4)
-        {
-            return new Company(_moviemetadata0, _seriesmetadata1, _musicalbummetadata2, _bookmetadata3, _company4);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for Id.
-        /// </summary>
-        internal int _Id;
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before setting.
-        /// </summary>
-        partial void SetId(int oldValue, ref int newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before returning.
-        /// </summary>
-        partial void GetId(ref int result);
-
-        /// <summary>
-        /// Identity, Indexed, Required.
-        /// </summary>
-        [Key]
-        [Required]
-        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
-        public int Id
-        {
-            get
-            {
-                int value = _Id;
-                GetId(ref value);
-                return _Id = value;
-            }
-
-            protected set
-            {
-                int oldValue = _Id;
-                SetId(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Id = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Required, ConcurrenyToken.
-        /// </summary>
-        [ConcurrencyCheck]
-        [Required]
-        public uint RowVersion { get; set; }
-
-        public void OnSavingChanges()
-        {
-            RowVersion++;
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-        [ForeignKey("CompanyMetadata_CompanyMetadata_Id")]
-        public virtual ICollection<CompanyMetadata> CompanyMetadata { get; protected set; }
-        [ForeignKey("Company_Parent_Id")]
-        public virtual Company Parent { get; set; }
-    }
-}
-

+ 0 - 230
Jellyfin.Data/Entities/CompanyMetadata.cs

@@ -1,230 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class CompanyMetadata : Metadata
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected CompanyMetadata()
-        {
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static CompanyMetadata CreateCompanyMetadataUnsafe()
-        {
-            return new CompanyMetadata();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="title">The title or name of the object.</param>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="_company0"></param>
-        public CompanyMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Company _company0)
-        {
-            if (string.IsNullOrEmpty(title))
-            {
-                throw new ArgumentNullException(nameof(title));
-            }
-
-            this.Title = title;
-
-            if (string.IsNullOrEmpty(language))
-            {
-                throw new ArgumentNullException(nameof(language));
-            }
-
-            this.Language = language;
-
-            if (_company0 == null)
-            {
-                throw new ArgumentNullException(nameof(_company0));
-            }
-
-            _company0.CompanyMetadata.Add(this);
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="title">The title or name of the object.</param>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="_company0"></param>
-        public static CompanyMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Company _company0)
-        {
-            return new CompanyMetadata(title, language, dateadded, datemodified, _company0);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for Description.
-        /// </summary>
-        protected string _Description;
-        /// <summary>
-        /// When provided in a partial class, allows value of Description to be changed before setting.
-        /// </summary>
-        partial void SetDescription(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Description to be changed before returning.
-        /// </summary>
-        partial void GetDescription(ref string result);
-
-        /// <summary>
-        /// Max length = 65535
-        /// </summary>
-        [MaxLength(65535)]
-        [StringLength(65535)]
-        public string Description
-        {
-            get
-            {
-                string value = _Description;
-                GetDescription(ref value);
-                return _Description = value;
-            }
-
-            set
-            {
-                string oldValue = _Description;
-                SetDescription(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Description = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Headquarters.
-        /// </summary>
-        protected string _Headquarters;
-        /// <summary>
-        /// When provided in a partial class, allows value of Headquarters to be changed before setting.
-        /// </summary>
-        partial void SetHeadquarters(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Headquarters to be changed before returning.
-        /// </summary>
-        partial void GetHeadquarters(ref string result);
-
-        /// <summary>
-        /// Max length = 255
-        /// </summary>
-        [MaxLength(255)]
-        [StringLength(255)]
-        public string Headquarters
-        {
-            get
-            {
-                string value = _Headquarters;
-                GetHeadquarters(ref value);
-                return _Headquarters = value;
-            }
-
-            set
-            {
-                string oldValue = _Headquarters;
-                SetHeadquarters(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Headquarters = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Country.
-        /// </summary>
-        protected string _Country;
-        /// <summary>
-        /// When provided in a partial class, allows value of Country to be changed before setting.
-        /// </summary>
-        partial void SetCountry(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Country to be changed before returning.
-        /// </summary>
-        partial void GetCountry(ref string result);
-
-        /// <summary>
-        /// Max length = 2
-        /// </summary>
-        [MaxLength(2)]
-        [StringLength(2)]
-        public string Country
-        {
-            get
-            {
-                string value = _Country;
-                GetCountry(ref value);
-                return _Country = value;
-            }
-
-            set
-            {
-                string oldValue = _Country;
-                SetCountry(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Country = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Homepage.
-        /// </summary>
-        protected string _Homepage;
-        /// <summary>
-        /// When provided in a partial class, allows value of Homepage to be changed before setting.
-        /// </summary>
-        partial void SetHomepage(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Homepage to be changed before returning.
-        /// </summary>
-        partial void GetHomepage(ref string result);
-
-        /// <summary>
-        /// Max length = 1024
-        /// </summary>
-        [MaxLength(1024)]
-        [StringLength(1024)]
-        public string Homepage
-        {
-            get
-            {
-                string value = _Homepage;
-                GetHomepage(ref value);
-                return _Homepage = value;
-            }
-
-            set
-            {
-                string oldValue = _Homepage;
-                SetHomepage(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Homepage = value;
-                }
-            }
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-    }
-}
-

+ 0 - 67
Jellyfin.Data/Entities/CustomItem.cs

@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class CustomItem : LibraryItem
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected CustomItem()
-        {
-            CustomItemMetadata = new HashSet<CustomItemMetadata>();
-            Releases = new HashSet<Release>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static CustomItem CreateCustomItemUnsafe()
-        {
-            return new CustomItem();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
-        public CustomItem(Guid urlid, DateTime dateadded)
-        {
-            this.UrlId = urlid;
-
-            this.CustomItemMetadata = new HashSet<CustomItemMetadata>();
-            this.Releases = new HashSet<Release>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
-        public static CustomItem Create(Guid urlid, DateTime dateadded)
-        {
-            return new CustomItem(urlid, dateadded);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-        [ForeignKey("CustomItemMetadata_CustomItemMetadata_Id")]
-        public virtual ICollection<CustomItemMetadata> CustomItemMetadata { get; protected set; }
-
-        [ForeignKey("Release_Releases_Id")]
-        public virtual ICollection<Release> Releases { get; protected set; }
-    }
-}
-

+ 0 - 77
Jellyfin.Data/Entities/CustomItemMetadata.cs

@@ -1,77 +0,0 @@
-using System;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class CustomItemMetadata : Metadata
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected CustomItemMetadata()
-        {
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static CustomItemMetadata CreateCustomItemMetadataUnsafe()
-        {
-            return new CustomItemMetadata();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="title">The title or name of the object.</param>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="_customitem0"></param>
-        public CustomItemMetadata(string title, string language, DateTime dateadded, DateTime datemodified, CustomItem _customitem0)
-        {
-            if (string.IsNullOrEmpty(title))
-            {
-                throw new ArgumentNullException(nameof(title));
-            }
-
-            this.Title = title;
-
-            if (string.IsNullOrEmpty(language))
-            {
-                throw new ArgumentNullException(nameof(language));
-            }
-
-            this.Language = language;
-
-            if (_customitem0 == null)
-            {
-                throw new ArgumentNullException(nameof(_customitem0));
-            }
-
-            _customitem0.CustomItemMetadata.Add(this);
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="title">The title or name of the object.</param>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="_customitem0"></param>
-        public static CustomItemMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, CustomItem _customitem0)
-        {
-            return new CustomItemMetadata(title, language, dateadded, datemodified, _customitem0);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-    }
-}
-

+ 0 - 114
Jellyfin.Data/Entities/Episode.cs

@@ -1,114 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class Episode : LibraryItem
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected Episode()
-        {
-            // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem.
-            // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
-
-            Releases = new HashSet<Release>();
-            EpisodeMetadata = new HashSet<EpisodeMetadata>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static Episode CreateEpisodeUnsafe()
-        {
-            return new Episode();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
-        /// <param name="_season0"></param>
-        public Episode(Guid urlid, DateTime dateadded, Season _season0)
-        {
-            // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem.
-            // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
-
-            this.UrlId = urlid;
-
-            if (_season0 == null)
-            {
-                throw new ArgumentNullException(nameof(_season0));
-            }
-
-            _season0.Episodes.Add(this);
-
-            this.Releases = new HashSet<Release>();
-            this.EpisodeMetadata = new HashSet<EpisodeMetadata>();
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
-        /// <param name="_season0"></param>
-        public static Episode Create(Guid urlid, DateTime dateadded, Season _season0)
-        {
-            return new Episode(urlid, dateadded, _season0);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for EpisodeNumber.
-        /// </summary>
-        protected int? _EpisodeNumber;
-        /// <summary>
-        /// When provided in a partial class, allows value of EpisodeNumber to be changed before setting.
-        /// </summary>
-        partial void SetEpisodeNumber(int? oldValue, ref int? newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of EpisodeNumber to be changed before returning.
-        /// </summary>
-        partial void GetEpisodeNumber(ref int? result);
-
-        public int? EpisodeNumber
-        {
-            get
-            {
-                int? value = _EpisodeNumber;
-                GetEpisodeNumber(ref value);
-                return _EpisodeNumber = value;
-            }
-
-            set
-            {
-                int? oldValue = _EpisodeNumber;
-                SetEpisodeNumber(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _EpisodeNumber = value;
-                }
-            }
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-        [ForeignKey("Release_Releases_Id")]
-        public virtual ICollection<Release> Releases { get; protected set; }
-        [ForeignKey("EpisodeMetadata_EpisodeMetadata_Id")]
-        public virtual ICollection<EpisodeMetadata> EpisodeMetadata { get; protected set; }
-    }
-}
-

+ 0 - 192
Jellyfin.Data/Entities/EpisodeMetadata.cs

@@ -1,192 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class EpisodeMetadata : Metadata
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected EpisodeMetadata()
-        {
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static EpisodeMetadata CreateEpisodeMetadataUnsafe()
-        {
-            return new EpisodeMetadata();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="title">The title or name of the object.</param>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="_episode0"></param>
-        public EpisodeMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Episode _episode0)
-        {
-            if (string.IsNullOrEmpty(title))
-            {
-                throw new ArgumentNullException(nameof(title));
-            }
-
-            this.Title = title;
-
-            if (string.IsNullOrEmpty(language))
-            {
-                throw new ArgumentNullException(nameof(language));
-            }
-
-            this.Language = language;
-
-            if (_episode0 == null)
-            {
-                throw new ArgumentNullException(nameof(_episode0));
-            }
-
-            _episode0.EpisodeMetadata.Add(this);
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="title">The title or name of the object.</param>
-        /// <param name="language">ISO-639-3 3-character language codes.</param>
-        /// <param name="_episode0"></param>
-        public static EpisodeMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Episode _episode0)
-        {
-            return new EpisodeMetadata(title, language, dateadded, datemodified, _episode0);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for Outline.
-        /// </summary>
-        protected string _Outline;
-        /// <summary>
-        /// When provided in a partial class, allows value of Outline to be changed before setting.
-        /// </summary>
-        partial void SetOutline(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Outline to be changed before returning.
-        /// </summary>
-        partial void GetOutline(ref string result);
-
-        /// <summary>
-        /// Max length = 1024
-        /// </summary>
-        [MaxLength(1024)]
-        [StringLength(1024)]
-        public string Outline
-        {
-            get
-            {
-                string value = _Outline;
-                GetOutline(ref value);
-                return _Outline = value;
-            }
-
-            set
-            {
-                string oldValue = _Outline;
-                SetOutline(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Outline = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Plot.
-        /// </summary>
-        protected string _Plot;
-        /// <summary>
-        /// When provided in a partial class, allows value of Plot to be changed before setting.
-        /// </summary>
-        partial void SetPlot(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Plot to be changed before returning.
-        /// </summary>
-        partial void GetPlot(ref string result);
-
-        /// <summary>
-        /// Max length = 65535
-        /// </summary>
-        [MaxLength(65535)]
-        [StringLength(65535)]
-        public string Plot
-        {
-            get
-            {
-                string value = _Plot;
-                GetPlot(ref value);
-                return _Plot = value;
-            }
-
-            set
-            {
-                string oldValue = _Plot;
-                SetPlot(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Plot = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Tagline.
-        /// </summary>
-        protected string _Tagline;
-        /// <summary>
-        /// When provided in a partial class, allows value of Tagline to be changed before setting.
-        /// </summary>
-        partial void SetTagline(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Tagline to be changed before returning.
-        /// </summary>
-        partial void GetTagline(ref string result);
-
-        /// <summary>
-        /// Max length = 1024
-        /// </summary>
-        [MaxLength(1024)]
-        [StringLength(1024)]
-        public string Tagline
-        {
-            get
-            {
-                string value = _Tagline;
-                GetTagline(ref value);
-                return _Tagline = value;
-            }
-
-            set
-            {
-                string oldValue = _Tagline;
-                SetTagline(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Tagline = value;
-                }
-            }
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-    }
-}
-

+ 0 - 160
Jellyfin.Data/Entities/Genre.cs

@@ -1,160 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class Genre
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected Genre()
-        {
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static Genre CreateGenreUnsafe()
-        {
-            return new Genre();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="name"></param>
-        /// <param name="_metadata0"></param>
-        public Genre(string name, Metadata _metadata0)
-        {
-            if (string.IsNullOrEmpty(name))
-            {
-                throw new ArgumentNullException(nameof(name));
-            }
-
-            this.Name = name;
-
-            if (_metadata0 == null)
-            {
-                throw new ArgumentNullException(nameof(_metadata0));
-            }
-
-            _metadata0.Genres.Add(this);
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="name"></param>
-        /// <param name="_metadata0"></param>
-        public static Genre Create(string name, Metadata _metadata0)
-        {
-            return new Genre(name, _metadata0);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Backing field for Id.
-        /// </summary>
-        internal int _Id;
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before setting.
-        /// </summary>
-        partial void SetId(int oldValue, ref int newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Id to be changed before returning.
-        /// </summary>
-        partial void GetId(ref int result);
-
-        /// <summary>
-        /// Identity, Indexed, Required.
-        /// </summary>
-        [Key]
-        [Required]
-        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
-        public int Id
-        {
-            get
-            {
-                int value = _Id;
-                GetId(ref value);
-                return _Id = value;
-            }
-
-            protected set
-            {
-                int oldValue = _Id;
-                SetId(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Id = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Backing field for Name.
-        /// </summary>
-        internal string _Name;
-        /// <summary>
-        /// When provided in a partial class, allows value of Name to be changed before setting.
-        /// </summary>
-        partial void SetName(string oldValue, ref string newValue);
-        /// <summary>
-        /// When provided in a partial class, allows value of Name to be changed before returning.
-        /// </summary>
-        partial void GetName(ref string result);
-
-        /// <summary>
-        /// Indexed, Required, Max length = 255
-        /// </summary>
-        [Required]
-        [MaxLength(255)]
-        [StringLength(255)]
-        public string Name
-        {
-            get
-            {
-                string value = _Name;
-                GetName(ref value);
-                return _Name = value;
-            }
-
-            set
-            {
-                string oldValue = _Name;
-                SetName(oldValue, ref value);
-                if (oldValue != value)
-                {
-                    _Name = value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Required, ConcurrenyToken.
-        /// </summary>
-        [ConcurrencyCheck]
-        [Required]
-        public uint RowVersion { get; set; }
-
-        public void OnSavingChanges()
-        {
-            RowVersion++;
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-    }
-}
-

+ 4 - 1
Jellyfin.Data/Entities/Group.cs

@@ -1,16 +1,19 @@
+#pragma warning disable CS1591
+
 using System;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.Linq;
 using Jellyfin.Data.Enums;
+using Jellyfin.Data.Interfaces;
 
 namespace Jellyfin.Data.Entities
 {
     /// <summary>
     /// An entity representing a group.
     /// </summary>
-    public partial class Group : IHasPermissions, ISavingChanges
+    public partial class Group : IHasPermissions, IHasConcurrencyToken
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="Group"/> class.

+ 3 - 1
Jellyfin.Data/Entities/ImageInfo.cs

@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CS1591
+
+using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 

+ 3 - 1
Jellyfin.Data/Entities/ItemDisplayPreferences.cs

@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CS1591
+
+using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using Jellyfin.Data.Enums;

+ 81 - 0
Jellyfin.Data/Entities/Libraries/Artwork.cs

@@ -0,0 +1,81 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Enums;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing artwork.
+    /// </summary>
+    public class Artwork : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Artwork"/> class.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="kind">The kind of art.</param>
+        /// <param name="owner">The owner.</param>
+        public Artwork(string path, ArtKind kind, IHasArtwork owner)
+        {
+            if (string.IsNullOrEmpty(path))
+            {
+                throw new ArgumentNullException(nameof(path));
+            }
+
+            Path = path;
+            Kind = kind;
+
+            owner?.Artwork.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Artwork"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected Artwork()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the path.
+        /// </summary>
+        /// <remarks>
+        /// Required, Max length = 65535.
+        /// </remarks>
+        [Required]
+        [MaxLength(65535)]
+        [StringLength(65535)]
+        public string Path { get; set; }
+
+        /// <summary>
+        /// Gets or sets the kind of artwork.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        public ArtKind Kind { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 28 - 0
Jellyfin.Data/Entities/Libraries/Book.cs

@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a book.
+    /// </summary>
+    public class Book : LibraryItem, IHasReleases
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Book"/> class.
+        /// </summary>
+        public Book()
+        {
+            BookMetadata = new HashSet<BookMetadata>();
+            Releases = new HashSet<Release>();
+        }
+
+        /// <summary>
+        /// Gets or sets a collection containing the metadata for this book.
+        /// </summary>
+        public virtual ICollection<BookMetadata> BookMetadata { get; protected set; }
+
+        /// <inheritdoc />
+        public virtual ICollection<Release> Releases { get; protected set; }
+    }
+}

+ 55 - 0
Jellyfin.Data/Entities/Libraries/BookMetadata.cs

@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity containing metadata for a book.
+    /// </summary>
+    public class BookMetadata : Metadata, IHasCompanies
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BookMetadata"/> class.
+        /// </summary>
+        /// <param name="title">The title or name of the object.</param>
+        /// <param name="language">ISO-639-3 3-character language codes.</param>
+        /// <param name="book">The book.</param>
+        public BookMetadata(string title, string language, Book book) : base(title, language)
+        {
+            if (book == null)
+            {
+                throw new ArgumentNullException(nameof(book));
+            }
+
+            book.BookMetadata.Add(this);
+
+            Publishers = new HashSet<Company>();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BookMetadata"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected BookMetadata()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the ISBN.
+        /// </summary>
+        public long? Isbn { get; set; }
+
+        /// <summary>
+        /// Gets or sets a collection of the publishers for this book.
+        /// </summary>
+        public virtual ICollection<Company> Publishers { get; protected set; }
+
+        /// <inheritdoc />
+        [NotMapped]
+        public ICollection<Company> Companies => Publishers;
+    }
+}

+ 102 - 0
Jellyfin.Data/Entities/Libraries/Chapter.cs

@@ -0,0 +1,102 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a chapter.
+    /// </summary>
+    public class Chapter : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Chapter"/> class.
+        /// </summary>
+        /// <param name="language">ISO-639-3 3-character language codes.</param>
+        /// <param name="startTime">The start time for this chapter.</param>
+        /// <param name="release">The release.</param>
+        public Chapter(string language, long startTime, Release release)
+        {
+            if (string.IsNullOrEmpty(language))
+            {
+                throw new ArgumentNullException(nameof(language));
+            }
+
+            Language = language;
+            StartTime = startTime;
+
+            if (release == null)
+            {
+                throw new ArgumentNullException(nameof(release));
+            }
+
+            release.Chapters.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Chapter"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected Chapter()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the language.
+        /// </summary>
+        /// <remarks>
+        /// Required, Min length = 3, Max length = 3
+        /// ISO-639-3 3-character language codes.
+        /// </remarks>
+        [Required]
+        [MinLength(3)]
+        [MaxLength(3)]
+        [StringLength(3)]
+        public string Language { get; set; }
+
+        /// <summary>
+        /// Gets or sets the start time.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        public long StartTime { get; set; }
+
+        /// <summary>
+        /// Gets or sets the end time.
+        /// </summary>
+        public long? EndTime { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; protected set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 55 - 0
Jellyfin.Data/Entities/Libraries/Collection.cs

@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a collection.
+    /// </summary>
+    public class Collection : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Collection"/> class.
+        /// </summary>
+        public Collection()
+        {
+            Items = new HashSet<CollectionItem>();
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Name { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing this collection's items.
+        /// </summary>
+        public virtual ICollection<CollectionItem> Items { get; protected set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 94 - 0
Jellyfin.Data/Entities/Libraries/CollectionItem.cs

@@ -0,0 +1,94 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a collection item.
+    /// </summary>
+    public class CollectionItem : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CollectionItem"/> class.
+        /// </summary>
+        /// <param name="collection">The collection.</param>
+        /// <param name="previous">The previous item.</param>
+        /// <param name="next">The next item.</param>
+        public CollectionItem(Collection collection, CollectionItem previous, CollectionItem next)
+        {
+            if (collection == null)
+            {
+                throw new ArgumentNullException(nameof(collection));
+            }
+
+            collection.Items.Add(this);
+
+            if (next != null)
+            {
+                Next = next;
+                next.Previous = this;
+            }
+
+            if (previous != null)
+            {
+                Previous = previous;
+                previous.Next = this;
+            }
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CollectionItem"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected CollectionItem()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <summary>
+        /// Gets or sets the library item.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        public virtual LibraryItem LibraryItem { get; set; }
+
+        /// <summary>
+        /// Gets or sets the next item in the collection.
+        /// </summary>
+        /// <remarks>
+        /// TODO check if this properly updated dependant and has the proper principal relationship
+        /// </remarks>
+        public virtual CollectionItem Next { get; set; }
+
+        /// <summary>
+        /// Gets or sets the previous item in the collection.
+        /// </summary>
+        /// <remarks>
+        /// TODO check if this properly updated dependant and has the proper principal relationship
+        /// </remarks>
+        public virtual CollectionItem Previous { get; set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 67 - 0
Jellyfin.Data/Entities/Libraries/Company.cs

@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a company.
+    /// </summary>
+    public class Company : IHasCompanies, IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Company"/> class.
+        /// </summary>
+        /// <param name="owner">The owner of this company.</param>
+        public Company(IHasCompanies owner)
+        {
+            owner?.Companies.Add(this);
+
+            CompanyMetadata = new HashSet<CompanyMetadata>();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Company"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected Company()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the metadata.
+        /// </summary>
+        public virtual ICollection<CompanyMetadata> CompanyMetadata { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing this company's child companies.
+        /// </summary>
+        public virtual ICollection<Company> ChildCompanies { get; protected set; }
+
+        /// <inheritdoc />
+        [NotMapped]
+        public ICollection<Company> Companies => ChildCompanies;
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 74 - 0
Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs

@@ -0,0 +1,74 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity holding metadata for a <see cref="Company"/>.
+    /// </summary>
+    public class CompanyMetadata : Metadata
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CompanyMetadata"/> class.
+        /// </summary>
+        /// <param name="title">The title or name of the object.</param>
+        /// <param name="language">ISO-639-3 3-character language codes.</param>
+        /// <param name="company">The company.</param>
+        public CompanyMetadata(string title, string language, Company company) : base(title, language)
+        {
+            if (company == null)
+            {
+                throw new ArgumentNullException(nameof(company));
+            }
+
+            company.CompanyMetadata.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CompanyMetadata"/> class.
+        /// </summary>
+        protected CompanyMetadata()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the description.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 65535.
+        /// </remarks>
+        [MaxLength(65535)]
+        [StringLength(65535)]
+        public string Description { get; set; }
+
+        /// <summary>
+        /// Gets or sets the headquarters.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 255.
+        /// </remarks>
+        [MaxLength(255)]
+        [StringLength(255)]
+        public string Headquarters { get; set; }
+
+        /// <summary>
+        /// Gets or sets the country code.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 2.
+        /// </remarks>
+        [MaxLength(2)]
+        [StringLength(2)]
+        public string Country { get; set; }
+
+        /// <summary>
+        /// Gets or sets the homepage.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Homepage { get; set; }
+    }
+}

+ 28 - 0
Jellyfin.Data/Entities/Libraries/CustomItem.cs

@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a custom item.
+    /// </summary>
+    public class CustomItem : LibraryItem, IHasReleases
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CustomItem"/> class.
+        /// </summary>
+        public CustomItem()
+        {
+            CustomItemMetadata = new HashSet<CustomItemMetadata>();
+            Releases = new HashSet<Release>();
+        }
+
+        /// <summary>
+        /// Gets or sets a collection containing the metadata for this item.
+        /// </summary>
+        public virtual ICollection<CustomItemMetadata> CustomItemMetadata { get; protected set; }
+
+        /// <inheritdoc />
+        public virtual ICollection<Release> Releases { get; protected set; }
+    }
+}

+ 36 - 0
Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs

@@ -0,0 +1,36 @@
+using System;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity containing metadata for a custom item.
+    /// </summary>
+    public class CustomItemMetadata : Metadata
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.
+        /// </summary>
+        /// <param name="title">The title or name of the object.</param>
+        /// <param name="language">ISO-639-3 3-character language codes.</param>
+        /// <param name="item">The item.</param>
+        public CustomItemMetadata(string title, string language, CustomItem item) : base(title, language)
+        {
+            if (item == null)
+            {
+                throw new ArgumentNullException(nameof(item));
+            }
+
+            item.CustomItemMetadata.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected CustomItemMetadata()
+        {
+        }
+    }
+}

+ 52 - 0
Jellyfin.Data/Entities/Libraries/Episode.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing an episode.
+    /// </summary>
+    public class Episode : LibraryItem, IHasReleases
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Episode"/> class.
+        /// </summary>
+        /// <param name="season">The season.</param>
+        public Episode(Season season)
+        {
+            if (season == null)
+            {
+                throw new ArgumentNullException(nameof(season));
+            }
+
+            season.Episodes.Add(this);
+
+            Releases = new HashSet<Release>();
+            EpisodeMetadata = new HashSet<EpisodeMetadata>();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Episode"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected Episode()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the episode number.
+        /// </summary>
+        public int? EpisodeNumber { get; set; }
+
+        /// <inheritdoc />
+        public virtual ICollection<Release> Releases { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the metadata for this episode.
+        /// </summary>
+        public virtual ICollection<EpisodeMetadata> EpisodeMetadata { get; protected set; }
+    }
+}

+ 67 - 0
Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs

@@ -0,0 +1,67 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity containing metadata for an <see cref="Episode"/>.
+    /// </summary>
+    public class EpisodeMetadata : Metadata
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.
+        /// </summary>
+        /// <param name="title">The title or name of the object.</param>
+        /// <param name="language">ISO-639-3 3-character language codes.</param>
+        /// <param name="episode">The episode.</param>
+        public EpisodeMetadata(string title, string language, Episode episode) : base(title, language)
+        {
+            if (episode == null)
+            {
+                throw new ArgumentNullException(nameof(episode));
+            }
+
+            episode.EpisodeMetadata.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected EpisodeMetadata()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the outline.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Outline { get; set; }
+
+        /// <summary>
+        /// Gets or sets the plot.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 65535.
+        /// </remarks>
+        [MaxLength(65535)]
+        [StringLength(65535)]
+        public string Plot { get; set; }
+
+        /// <summary>
+        /// Gets or sets the tagline.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Tagline { get; set; }
+    }
+}

+ 75 - 0
Jellyfin.Data/Entities/Libraries/Genre.cs

@@ -0,0 +1,75 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a genre.
+    /// </summary>
+    public class Genre : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Genre"/> class.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <param name="metadata">The metadata.</param>
+        public Genre(string name, Metadata metadata)
+        {
+            if (string.IsNullOrEmpty(name))
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Name = name;
+
+            if (metadata == null)
+            {
+                throw new ArgumentNullException(nameof(metadata));
+            }
+
+            metadata.Genres.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Genre"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected Genre()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <remarks>
+        /// Indexed, Required, Max length = 255.
+        /// </remarks>
+        [Required]
+        [MaxLength(255)]
+        [StringLength(255)]
+        public string Name { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; protected set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 76 - 0
Jellyfin.Data/Entities/Libraries/Library.cs

@@ -0,0 +1,76 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a library.
+    /// </summary>
+    public class Library : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Library"/> class.
+        /// </summary>
+        /// <param name="name">The name of the library.</param>
+        public Library(string name)
+        {
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Name = name;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Library"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected Library()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <remarks>
+        /// Required, Max length = 128.
+        /// </remarks>
+        [Required]
+        [MaxLength(128)]
+        [StringLength(128)]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the root path of the library.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        [Required]
+        public string Path { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 63 - 0
Jellyfin.Data/Entities/Libraries/LibraryItem.cs

@@ -0,0 +1,63 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a library item.
+    /// </summary>
+    public abstract class LibraryItem : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LibraryItem"/> class.
+        /// </summary>
+        /// <param name="library">The library of this item.</param>
+        protected LibraryItem(Library library)
+        {
+            DateAdded = DateTime.UtcNow;
+            Library = library;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LibraryItem"/> class.
+        /// </summary>
+        protected LibraryItem()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the date this library item was added.
+        /// </summary>
+        public DateTime DateAdded { get; protected set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the library of this item.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        [Required]
+        public virtual Library Library { get; set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 94 - 0
Jellyfin.Data/Entities/Libraries/MediaFile.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Enums;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a file on disk.
+    /// </summary>
+    public class MediaFile : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MediaFile"/> class.
+        /// </summary>
+        /// <param name="path">The path relative to the LibraryRoot.</param>
+        /// <param name="kind">The file kind.</param>
+        /// <param name="release">The release.</param>
+        public MediaFile(string path, MediaFileKind kind, Release release)
+        {
+            if (string.IsNullOrEmpty(path))
+            {
+                throw new ArgumentNullException(nameof(path));
+            }
+
+            Path = path;
+            Kind = kind;
+
+            if (release == null)
+            {
+                throw new ArgumentNullException(nameof(release));
+            }
+
+            release.MediaFiles.Add(this);
+
+            MediaFileStreams = new HashSet<MediaFileStream>();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MediaFile"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected MediaFile()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the path relative to the library root.
+        /// </summary>
+        /// <remarks>
+        /// Required, Max length = 65535.
+        /// </remarks>
+        [Required]
+        [MaxLength(65535)]
+        [StringLength(65535)]
+        public string Path { get; set; }
+
+        /// <summary>
+        /// Gets or sets the kind of media file.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        public MediaFileKind Kind { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the streams in this file.
+        /// </summary>
+        public virtual ICollection<MediaFileStream> MediaFileStreams { get; protected set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 67 - 0
Jellyfin.Data/Entities/Libraries/MediaFileStream.cs

@@ -0,0 +1,67 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a stream in a media file.
+    /// </summary>
+    public class MediaFileStream : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MediaFileStream"/> class.
+        /// </summary>
+        /// <param name="streamNumber">The number of this stream.</param>
+        /// <param name="mediaFile">The media file.</param>
+        public MediaFileStream(int streamNumber, MediaFile mediaFile)
+        {
+            StreamNumber = streamNumber;
+
+            if (mediaFile == null)
+            {
+                throw new ArgumentNullException(nameof(mediaFile));
+            }
+
+            mediaFile.MediaFileStreams.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MediaFileStream"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected MediaFileStream()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the stream number.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        public int StreamNumber { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 165 - 0
Jellyfin.Data/Entities/Libraries/Metadata.cs

@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An abstract class that holds metadata.
+    /// </summary>
+    public abstract class Metadata : IHasArtwork, IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Metadata"/> class.
+        /// </summary>
+        /// <param name="title">The title or name of the object.</param>
+        /// <param name="language">ISO-639-3 3-character language codes.</param>
+        protected Metadata(string title, string language)
+        {
+            if (string.IsNullOrEmpty(title))
+            {
+                throw new ArgumentNullException(nameof(title));
+            }
+
+            if (string.IsNullOrEmpty(language))
+            {
+                throw new ArgumentNullException(nameof(language));
+            }
+
+            Title = title;
+            Language = language;
+            DateAdded = DateTime.UtcNow;
+            DateModified = DateAdded;
+
+            PersonRoles = new HashSet<PersonRole>();
+            Genres = new HashSet<Genre>();
+            Artwork = new HashSet<Artwork>();
+            Ratings = new HashSet<Rating>();
+            Sources = new HashSet<MetadataProviderId>();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Metadata"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to being abstract.
+        /// </remarks>
+        protected Metadata()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the title.
+        /// </summary>
+        /// <remarks>
+        /// Required, Max length = 1024.
+        /// </remarks>
+        [Required]
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Title { get; set; }
+
+        /// <summary>
+        /// Gets or sets the original title.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string OriginalTitle { get; set; }
+
+        /// <summary>
+        /// Gets or sets the sort title.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string SortTitle { get; set; }
+
+        /// <summary>
+        /// Gets or sets the language.
+        /// </summary>
+        /// <remarks>
+        /// Required, Min length = 3, Max length = 3.
+        /// ISO-639-3 3-character language codes.
+        /// </remarks>
+        [Required]
+        [MinLength(3)]
+        [MaxLength(3)]
+        [StringLength(3)]
+        public string Language { get; set; }
+
+        /// <summary>
+        /// Gets or sets the release date.
+        /// </summary>
+        public DateTimeOffset? ReleaseDate { get; set; }
+
+        /// <summary>
+        /// Gets or sets the date added.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        public DateTime DateAdded { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the date modified.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        public DateTime DateModified { get; set; }
+
+        /// <summary>
+        /// Gets or sets the row version.
+        /// </summary>
+        /// <remarks>
+        /// Required, ConcurrencyToken.
+        /// </remarks>
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the person roles for this item.
+        /// </summary>
+        public virtual ICollection<PersonRole> PersonRoles { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the generes for this item.
+        /// </summary>
+        public virtual ICollection<Genre> Genres { get; protected set; }
+
+        /// <inheritdoc />
+        public virtual ICollection<Artwork> Artwork { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the ratings for this item.
+        /// </summary>
+        public virtual ICollection<Rating> Ratings { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the metadata sources for this item.
+        /// </summary>
+        public virtual ICollection<MetadataProviderId> Sources { get; protected set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 67 - 0
Jellyfin.Data/Entities/Libraries/MetadataProvider.cs

@@ -0,0 +1,67 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a metadata provider.
+    /// </summary>
+    public class MetadataProvider : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MetadataProvider"/> class.
+        /// </summary>
+        /// <param name="name">The name of the metadata provider.</param>
+        public MetadataProvider(string name)
+        {
+            if (string.IsNullOrEmpty(name))
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Name = name;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MetadataProvider"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected MetadataProvider()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <remarks>
+        /// Required, Max length = 1024.
+        /// </remarks>
+        [Required]
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Name { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 83 - 0
Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs

@@ -0,0 +1,83 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a unique identifier for a metadata provider.
+    /// </summary>
+    public class MetadataProviderId : IHasConcurrencyToken
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
+        /// </summary>
+        /// <param name="providerId">The provider id.</param>
+        /// <param name="metadata">The metadata entity.</param>
+        public MetadataProviderId(string providerId, Metadata metadata)
+        {
+            if (string.IsNullOrEmpty(providerId))
+            {
+                throw new ArgumentNullException(nameof(providerId));
+            }
+
+            ProviderId = providerId;
+
+            if (metadata == null)
+            {
+                throw new ArgumentNullException(nameof(metadata));
+            }
+
+            metadata.Sources.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected MetadataProviderId()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the provider id.
+        /// </summary>
+        /// <remarks>
+        /// Required, Max length = 255.
+        /// </remarks>
+        [Required]
+        [MaxLength(255)]
+        [StringLength(255)]
+        public string ProviderId { get; set; }
+
+        /// <inheritdoc />
+        [ConcurrencyCheck]
+        public uint RowVersion { get; set; }
+
+        /// <summary>
+        /// Gets or sets the metadata provider.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
+        public virtual MetadataProvider MetadataProvider { get; set; }
+
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+    }
+}

+ 28 - 0
Jellyfin.Data/Entities/Libraries/Movie.cs

@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a movie.
+    /// </summary>
+    public class Movie : LibraryItem, IHasReleases
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Movie"/> class.
+        /// </summary>
+        public Movie()
+        {
+            Releases = new HashSet<Release>();
+            MovieMetadata = new HashSet<MovieMetadata>();
+        }
+
+        /// <inheritdoc />
+        public virtual ICollection<Release> Releases { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the metadata for this movie.
+        /// </summary>
+        public virtual ICollection<MovieMetadata> MovieMetadata { get; protected set; }
+    }
+}

+ 85 - 0
Jellyfin.Data/Entities/Libraries/MovieMetadata.cs

@@ -0,0 +1,85 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Jellyfin.Data.Interfaces;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity holding the metadata for a movie.
+    /// </summary>
+    public class MovieMetadata : Metadata, IHasCompanies
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MovieMetadata"/> class.
+        /// </summary>
+        /// <param name="title">The title or name of the movie.</param>
+        /// <param name="language">ISO-639-3 3-character language codes.</param>
+        /// <param name="movie">The movie.</param>
+        public MovieMetadata(string title, string language, Movie movie) : base(title, language)
+        {
+            Studios = new HashSet<Company>();
+
+            movie.MovieMetadata.Add(this);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MovieMetadata"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected MovieMetadata()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the outline.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Outline { get; set; }
+
+        /// <summary>
+        /// Gets or sets the tagline.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 1024.
+        /// </remarks>
+        [MaxLength(1024)]
+        [StringLength(1024)]
+        public string Tagline { get; set; }
+
+        /// <summary>
+        /// Gets or sets the plot.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 65535.
+        /// </remarks>
+        [MaxLength(65535)]
+        [StringLength(65535)]
+        public string Plot { get; set; }
+
+        /// <summary>
+        /// Gets or sets the country code.
+        /// </summary>
+        /// <remarks>
+        /// Max length = 2.
+        /// </remarks>
+        [MaxLength(2)]
+        [StringLength(2)]
+        public string Country { get; set; }
+
+        /// <summary>
+        /// Gets or sets the studios that produced this movie.
+        /// </summary>
+        public virtual ICollection<Company> Studios { get; protected set; }
+
+        /// <inheritdoc />
+        [NotMapped]
+        public ICollection<Company> Companies => Studios;
+    }
+}

+ 29 - 0
Jellyfin.Data/Entities/Libraries/MusicAlbum.cs

@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+
+namespace Jellyfin.Data.Entities.Libraries
+{
+    /// <summary>
+    /// An entity representing a music album.
+    /// </summary>
+    public class MusicAlbum : LibraryItem
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MusicAlbum"/> class.
+        /// </summary>
+        public MusicAlbum()
+        {
+            MusicAlbumMetadata = new HashSet<MusicAlbumMetadata>();
+            Tracks = new HashSet<Track>();
+        }
+
+        /// <summary>
+        /// Gets or sets a collection containing the album metadata.
+        /// </summary>
+        public virtual ICollection<MusicAlbumMetadata> MusicAlbumMetadata { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets a collection containing the tracks.
+        /// </summary>
+        public virtual ICollection<Track> Tracks { get; protected set; }
+    }
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است