Browse Source

Migrate activity db to EF Core

Patrick Barron 5 years ago
parent
commit
032de931b1
27 changed files with 5147 additions and 1702 deletions
  1. 159 129
      Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
  2. 0 70
      Emby.Server.Implementations/Activity/ActivityManager.cs
  3. 0 308
      Emby.Server.Implementations/Activity/ActivityRepository.cs
  4. 10 4
      Emby.Server.Implementations/ApplicationHost.cs
  5. 3 2
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  6. 0 1140
      Jellyfin.Data/DbContexts/Jellyfin.cs
  7. 153 0
      Jellyfin.Data/Entities/ActivityLog.cs
  8. 9 0
      Jellyfin.Data/ISavingChanges.cs
  9. 21 3
      Jellyfin.Data/Jellyfin.Data.csproj
  10. 103 0
      Jellyfin.Server.Implementations/Activity/ActivityManager.cs
  11. 34 0
      Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
  12. 119 0
      Jellyfin.Server.Implementations/JellyfinDb.cs
  13. 33 0
      Jellyfin.Server.Implementations/JellyfinDbProvider.cs
  14. 1513 0
      Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs
  15. 1294 0
      Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs
  16. 20 0
      Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs
  17. 1511 0
      Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
  18. 7 0
      Jellyfin.Server/Jellyfin.Server.csproj
  19. 2 1
      Jellyfin.Server/Migrations/MigrationRunner.cs
  20. 109 0
      Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
  21. 6 5
      MediaBrowser.Api/Library/LibraryService.cs
  22. 1 1
      MediaBrowser.Api/System/ActivityLogService.cs
  23. 1 0
      MediaBrowser.Model/Activity/ActivityLogEntry.cs
  24. 12 3
      MediaBrowser.Model/Activity/IActivityManager.cs
  25. 0 14
      MediaBrowser.Model/Activity/IActivityRepository.cs
  26. 3 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  27. 24 22
      MediaBrowser.sln

+ 159 - 129
Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs

@@ -4,11 +4,11 @@ using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
@@ -104,47 +104,53 @@ namespace Emby.Server.Implementations.Activity
             return Task.CompletedTask;
             return Task.CompletedTask;
         }
         }
 
 
-        private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
+        private async void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("CameraImageUploadedFrom"),
                     _localization.GetLocalizedString("CameraImageUploadedFrom"),
                     e.Argument.Device.Name),
                     e.Argument.Device.Name),
-                Type = NotificationType.CameraImageUploaded.ToString()
-            });
+                NotificationType.CameraImageUploaded.ToString(),
+                Guid.Empty,
+                DateTime.UtcNow,
+                LogLevel.Trace))
+                .ConfigureAwait(false);
         }
         }
 
 
-        private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
+        private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserLockedOutWithName"),
                     _localization.GetLocalizedString("UserLockedOutWithName"),
                     e.Argument.Name),
                     e.Argument.Name),
-                Type = NotificationType.UserLockedOut.ToString(),
-                UserId = e.Argument.Id
-            });
+                NotificationType.UserLockedOut.ToString(),
+                e.Argument.Id,
+                DateTime.UtcNow,
+                LogLevel.Trace))
+                .ConfigureAwait(false);
         }
         }
 
 
-        private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
+        private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
                     _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
                     e.Provider,
                     e.Provider,
-                    Notifications.NotificationEntryPoint.GetItemName(e.Item)),
-                Type = "SubtitleDownloadFailure",
+                    Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
+                "SubtitleDownloadFailure",
+                Guid.Empty,
+                DateTime.UtcNow,
+                LogLevel.Trace)
+            {
                 ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
                 ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
                 ShortOverview = e.Exception.Message
                 ShortOverview = e.Exception.Message
-            });
+            }).ConfigureAwait(false);
         }
         }
 
 
-        private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
+        private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
         {
         {
             var item = e.MediaInfo;
             var item = e.MediaInfo;
 
 
@@ -167,20 +173,21 @@ namespace Emby.Server.Implementations.Activity
 
 
             var user = e.Users[0];
             var user = e.Users[0];
 
 
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
                     _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
                     user.Name,
                     user.Name,
                     GetItemName(item),
                     GetItemName(item),
                     e.DeviceName),
                     e.DeviceName),
-                Type = GetPlaybackStoppedNotificationType(item.MediaType),
-                UserId = user.Id
-            });
+                GetPlaybackStoppedNotificationType(item.MediaType),
+                user.Id,
+                DateTime.UtcNow,
+                LogLevel.Trace))
+                .ConfigureAwait(false);
         }
         }
 
 
-        private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
+        private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
         {
         {
             var item = e.MediaInfo;
             var item = e.MediaInfo;
 
 
@@ -203,17 +210,18 @@ namespace Emby.Server.Implementations.Activity
 
 
             var user = e.Users.First();
             var user = e.Users.First();
 
 
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
                     _localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
                     user.Name,
                     user.Name,
                     GetItemName(item),
                     GetItemName(item),
                     e.DeviceName),
                     e.DeviceName),
-                Type = GetPlaybackNotificationType(item.MediaType),
-                UserId = user.Id
-            });
+                GetPlaybackNotificationType(item.MediaType),
+                user.Id,
+                DateTime.UtcNow,
+                LogLevel.Trace))
+                .ConfigureAwait(false);
         }
         }
 
 
         private static string GetItemName(BaseItemDto item)
         private static string GetItemName(BaseItemDto item)
@@ -263,7 +271,7 @@ namespace Emby.Server.Implementations.Activity
             return null;
             return null;
         }
         }
 
 
-        private void OnSessionEnded(object sender, SessionEventArgs e)
+        private async void OnSessionEnded(object sender, SessionEventArgs e)
         {
         {
             var session = e.SessionInfo;
             var session = e.SessionInfo;
 
 
@@ -272,110 +280,120 @@ namespace Emby.Server.Implementations.Activity
                 return;
                 return;
             }
             }
 
 
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserOfflineFromDevice"),
                     _localization.GetLocalizedString("UserOfflineFromDevice"),
                     session.UserName,
                     session.UserName,
                     session.DeviceName),
                     session.DeviceName),
-                Type = "SessionEnded",
+                "SessionEnded",
+                session.UserId,
+                DateTime.UtcNow,
+                LogLevel.Trace)
+            {
                 ShortOverview = string.Format(
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("LabelIpAddressValue"),
                     _localization.GetLocalizedString("LabelIpAddressValue"),
                     session.RemoteEndPoint),
                     session.RemoteEndPoint),
-                UserId = session.UserId
-            });
+            }).ConfigureAwait(false);
         }
         }
 
 
-        private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
+        private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
         {
         {
             var user = e.Argument.User;
             var user = e.Argument.User;
 
 
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
                     _localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
                     user.Name),
                     user.Name),
-                Type = "AuthenticationSucceeded",
+                "AuthenticationSucceeded",
+                user.Id,
+                DateTime.UtcNow,
+                LogLevel.Trace)
+            {
                 ShortOverview = string.Format(
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("LabelIpAddressValue"),
                     _localization.GetLocalizedString("LabelIpAddressValue"),
                     e.Argument.SessionInfo.RemoteEndPoint),
                     e.Argument.SessionInfo.RemoteEndPoint),
-                UserId = user.Id
-            });
+            }).ConfigureAwait(false);
         }
         }
 
 
-        private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
+        private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
                     _localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
                     e.Argument.Username),
                     e.Argument.Username),
-                Type = "AuthenticationFailed",
+                "AuthenticationFailed",
+                Guid.Empty,
+                DateTime.UtcNow,
+                LogLevel.Error)
+            {
                 ShortOverview = string.Format(
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("LabelIpAddressValue"),
                     _localization.GetLocalizedString("LabelIpAddressValue"),
                     e.Argument.RemoteEndPoint),
                     e.Argument.RemoteEndPoint),
-                Severity = LogLevel.Error
-            });
+            }).ConfigureAwait(false);
         }
         }
 
 
-        private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
+        private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserPolicyUpdatedWithName"),
                     _localization.GetLocalizedString("UserPolicyUpdatedWithName"),
                     e.Argument.Name),
                     e.Argument.Name),
-                Type = "UserPolicyUpdated",
-                UserId = e.Argument.Id
-            });
+                "UserPolicyUpdated",
+                e.Argument.Id,
+                DateTime.UtcNow,
+                LogLevel.Trace))
+                .ConfigureAwait(false);
         }
         }
 
 
-        private void OnUserDeleted(object sender, GenericEventArgs<User> e)
+        private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserDeletedWithName"),
                     _localization.GetLocalizedString("UserDeletedWithName"),
                     e.Argument.Name),
                     e.Argument.Name),
-                Type = "UserDeleted"
-            });
+                "UserDeleted",
+                Guid.Empty,
+                DateTime.UtcNow,
+                LogLevel.Trace))
+                .ConfigureAwait(false);
         }
         }
 
 
-        private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
+        private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserPasswordChangedWithName"),
                     _localization.GetLocalizedString("UserPasswordChangedWithName"),
                     e.Argument.Name),
                     e.Argument.Name),
-                Type = "UserPasswordChanged",
-                UserId = e.Argument.Id
-            });
+                "UserPasswordChanged",
+                e.Argument.Id,
+                DateTime.UtcNow,
+                LogLevel.Trace)).ConfigureAwait(false);
         }
         }
 
 
-        private void OnUserCreated(object sender, GenericEventArgs<User> e)
+        private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserCreatedWithName"),
                     _localization.GetLocalizedString("UserCreatedWithName"),
                     e.Argument.Name),
                     e.Argument.Name),
-                Type = "UserCreated",
-                UserId = e.Argument.Id
-            });
+                "UserCreated",
+                e.Argument.Id,
+                DateTime.UtcNow,
+                LogLevel.Trace))
+                .ConfigureAwait(false);
         }
         }
 
 
-        private void OnSessionStarted(object sender, SessionEventArgs e)
+        private async void OnSessionStarted(object sender, SessionEventArgs e)
         {
         {
             var session = e.SessionInfo;
             var session = e.SessionInfo;
 
 
@@ -384,87 +402,100 @@ namespace Emby.Server.Implementations.Activity
                 return;
                 return;
             }
             }
 
 
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("UserOnlineFromDevice"),
                     _localization.GetLocalizedString("UserOnlineFromDevice"),
                     session.UserName,
                     session.UserName,
                     session.DeviceName),
                     session.DeviceName),
-                Type = "SessionStarted",
+                "SessionStarted",
+                session.UserId,
+                DateTime.UtcNow,
+                LogLevel.Trace)
+            {
                 ShortOverview = string.Format(
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("LabelIpAddressValue"),
                     _localization.GetLocalizedString("LabelIpAddressValue"),
-                    session.RemoteEndPoint),
-                UserId = session.UserId
-            });
+                    session.RemoteEndPoint)
+            }).ConfigureAwait(false);
         }
         }
 
 
-        private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
+        private async void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("PluginUpdatedWithName"),
                     _localization.GetLocalizedString("PluginUpdatedWithName"),
                     e.Argument.Item1.Name),
                     e.Argument.Item1.Name),
-                Type = NotificationType.PluginUpdateInstalled.ToString(),
+                NotificationType.PluginUpdateInstalled.ToString(),
+                Guid.Empty,
+                DateTime.UtcNow,
+                LogLevel.Trace)
+            {
                 ShortOverview = string.Format(
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("VersionNumber"),
                     _localization.GetLocalizedString("VersionNumber"),
                     e.Argument.Item2.version),
                     e.Argument.Item2.version),
                 Overview = e.Argument.Item2.changelog
                 Overview = e.Argument.Item2.changelog
-            });
+            }).ConfigureAwait(false);
         }
         }
 
 
-        private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
+        private async void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("PluginUninstalledWithName"),
                     _localization.GetLocalizedString("PluginUninstalledWithName"),
                     e.Argument.Name),
                     e.Argument.Name),
-                Type = NotificationType.PluginUninstalled.ToString()
-            });
+                NotificationType.PluginUninstalled.ToString(),
+                Guid.Empty,
+                DateTime.UtcNow,
+                LogLevel.Trace))
+                .ConfigureAwait(false);
         }
         }
 
 
-        private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
+        private async void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
         {
         {
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("PluginInstalledWithName"),
                     _localization.GetLocalizedString("PluginInstalledWithName"),
                     e.Argument.name),
                     e.Argument.name),
-                Type = NotificationType.PluginInstalled.ToString(),
+                NotificationType.PluginInstalled.ToString(),
+                Guid.Empty,
+                DateTime.UtcNow,
+                LogLevel.Trace)
+            {
                 ShortOverview = string.Format(
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("VersionNumber"),
                     _localization.GetLocalizedString("VersionNumber"),
                     e.Argument.version)
                     e.Argument.version)
-            });
+            }).ConfigureAwait(false);
         }
         }
 
 
-        private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
+        private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
         {
         {
             var installationInfo = e.InstallationInfo;
             var installationInfo = e.InstallationInfo;
 
 
-            CreateLogEntry(new ActivityLogEntry
-            {
-                Name = string.Format(
+            await CreateLogEntry(new ActivityLog(
+                string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("NameInstallFailed"),
                     _localization.GetLocalizedString("NameInstallFailed"),
                     installationInfo.Name),
                     installationInfo.Name),
-                Type = NotificationType.InstallationFailed.ToString(),
+                NotificationType.InstallationFailed.ToString(),
+                Guid.Empty,
+                DateTime.UtcNow,
+                LogLevel.Trace)
+            {
                 ShortOverview = string.Format(
                 ShortOverview = string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
                     _localization.GetLocalizedString("VersionNumber"),
                     _localization.GetLocalizedString("VersionNumber"),
                     installationInfo.Version),
                     installationInfo.Version),
                 Overview = e.Exception.Message
                 Overview = e.Exception.Message
-            });
+            }).ConfigureAwait(false);
         }
         }
 
 
-        private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
+        private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
         {
         {
             var result = e.Result;
             var result = e.Result;
             var task = e.Task;
             var task = e.Task;
@@ -495,22 +526,21 @@ namespace Emby.Server.Implementations.Activity
                     vals.Add(e.Result.LongErrorMessage);
                     vals.Add(e.Result.LongErrorMessage);
                 }
                 }
 
 
-                CreateLogEntry(new ActivityLogEntry
+                await CreateLogEntry(new ActivityLog(
+                    string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
+                    NotificationType.TaskFailed.ToString(),
+                    Guid.Empty,
+                    DateTime.UtcNow,
+                    LogLevel.Error)
                 {
                 {
-                    Name = string.Format(
-                        CultureInfo.InvariantCulture,
-                        _localization.GetLocalizedString("ScheduledTaskFailedWithName"),
-                        task.Name),
-                    Type = NotificationType.TaskFailed.ToString(),
                     Overview = string.Join(Environment.NewLine, vals),
                     Overview = string.Join(Environment.NewLine, vals),
-                    ShortOverview = runningTime,
-                    Severity = LogLevel.Error
-                });
+                    ShortOverview = runningTime
+                }).ConfigureAwait(false);
             }
             }
         }
         }
 
 
-        private void CreateLogEntry(ActivityLogEntry entry)
-            => _activityManager.Create(entry);
+        private async Task CreateLogEntry(ActivityLog entry)
+            => await _activityManager.CreateAsync(entry).ConfigureAwait(false);
 
 
         /// <inheritdoc />
         /// <inheritdoc />
         public void Dispose()
         public void Dispose()
@@ -558,7 +588,7 @@ namespace Emby.Server.Implementations.Activity
             {
             {
                 int years = days / DaysInYear;
                 int years = days / DaysInYear;
                 values.Add(CreateValueString(years, "year"));
                 values.Add(CreateValueString(years, "year"));
-                days %= DaysInYear;
+                days = days % DaysInYear;
             }
             }
 
 
             // Number of months
             // Number of months
@@ -566,7 +596,7 @@ namespace Emby.Server.Implementations.Activity
             {
             {
                 int months = days / DaysInMonth;
                 int months = days / DaysInMonth;
                 values.Add(CreateValueString(months, "month"));
                 values.Add(CreateValueString(months, "month"));
-                days %= DaysInMonth;
+                days = days % DaysInMonth;
             }
             }
 
 
             // Number of days
             // Number of days

+ 0 - 70
Emby.Server.Implementations/Activity/ActivityManager.cs

@@ -1,70 +0,0 @@
-using System;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Activity;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Querying;
-
-namespace Emby.Server.Implementations.Activity
-{
-    /// <summary>
-    /// The activity log manager.
-    /// </summary>
-    public class ActivityManager : IActivityManager
-    {
-        private readonly IActivityRepository _repo;
-        private readonly IUserManager _userManager;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ActivityManager"/> class.
-        /// </summary>
-        /// <param name="repo">The activity repository.</param>
-        /// <param name="userManager">The user manager.</param>
-        public ActivityManager(IActivityRepository repo, IUserManager userManager)
-        {
-            _repo = repo;
-            _userManager = userManager;
-        }
-
-        /// <inheritdoc />
-        public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
-
-        public void Create(ActivityLogEntry entry)
-        {
-            entry.Date = DateTime.UtcNow;
-
-            _repo.Create(entry);
-
-            EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
-        }
-
-        /// <inheritdoc />
-        public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
-        {
-            var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
-
-            foreach (var item in result.Items)
-            {
-                if (item.UserId == Guid.Empty)
-                {
-                    continue;
-                }
-
-                var user = _userManager.GetUserById(item.UserId);
-
-                if (user != null)
-                {
-                    var dto = _userManager.GetUserDto(user);
-                    item.UserPrimaryImageTag = dto.PrimaryImageTag;
-                }
-            }
-
-            return result;
-        }
-
-        /// <inheritdoc />
-        public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
-        {
-            return GetActivityLogEntries(minDate, null, startIndex, limit);
-        }
-    }
-}

+ 0 - 308
Emby.Server.Implementations/Activity/ActivityRepository.cs

@@ -1,308 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using Emby.Server.Implementations.Data;
-using MediaBrowser.Controller;
-using MediaBrowser.Model.Activity;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Querying;
-using Microsoft.Extensions.Logging;
-using SQLitePCL.pretty;
-
-namespace Emby.Server.Implementations.Activity
-{
-    /// <summary>
-    /// The activity log repository.
-    /// </summary>
-    public class ActivityRepository : BaseSqliteRepository, IActivityRepository
-    {
-        private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
-
-        private readonly IFileSystem _fileSystem;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ActivityRepository"/> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="appPaths">The server application paths.</param>
-        /// <param name="fileSystem">The filesystem.</param>
-        public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
-            : base(logger)
-        {
-            DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
-            _fileSystem = fileSystem;
-        }
-
-        /// <summary>
-        /// Initializes the <see cref="ActivityRepository"/>.
-        /// </summary>
-        public void Initialize()
-        {
-            try
-            {
-                InitializeInternal();
-            }
-            catch (Exception ex)
-            {
-                Logger.LogError(ex, "Error loading database file. Will reset and retry.");
-
-                _fileSystem.DeleteFile(DbFilePath);
-
-                InitializeInternal();
-            }
-        }
-
-        private void InitializeInternal()
-        {
-            using var connection = GetConnection();
-            connection.RunQueries(new[]
-            {
-                "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
-                "drop index if exists idx_ActivityLogEntries"
-            });
-
-            TryMigrate(connection);
-        }
-
-        private void TryMigrate(ManagedConnection connection)
-        {
-            try
-            {
-                if (TableExists(connection, "ActivityLogEntries"))
-                {
-                    connection.RunQueries(new[]
-                    {
-                        "INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
-                        "drop table if exists ActivityLogEntries"
-                    });
-                }
-            }
-            catch (Exception ex)
-            {
-                Logger.LogError(ex, "Error migrating activity log database");
-            }
-        }
-
-        /// <inheritdoc />
-        public void Create(ActivityLogEntry entry)
-        {
-            if (entry == null)
-            {
-                throw new ArgumentNullException(nameof(entry));
-            }
-
-            using var connection = GetConnection();
-            connection.RunInTransaction(db =>
-            {
-                using var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)");
-                statement.TryBind("@Name", entry.Name);
-
-                statement.TryBind("@Overview", entry.Overview);
-                statement.TryBind("@ShortOverview", entry.ShortOverview);
-                statement.TryBind("@Type", entry.Type);
-                statement.TryBind("@ItemId", entry.ItemId);
-
-                if (entry.UserId.Equals(Guid.Empty))
-                {
-                    statement.TryBindNull("@UserId");
-                }
-                else
-                {
-                    statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
-                }
-
-                statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
-                statement.TryBind("@LogSeverity", entry.Severity.ToString());
-
-                statement.MoveNext();
-            }, TransactionMode);
-        }
-
-        /// <summary>
-        /// Adds the provided <see cref="ActivityLogEntry"/> to this repository.
-        /// </summary>
-        /// <param name="entry">The activity log entry.</param>
-        /// <exception cref="ArgumentNullException">If entry is null.</exception>
-        public void Update(ActivityLogEntry entry)
-        {
-            if (entry == null)
-            {
-                throw new ArgumentNullException(nameof(entry));
-            }
-
-            using var connection = GetConnection();
-            connection.RunInTransaction(db =>
-            {
-                using var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id");
-                statement.TryBind("@Id", entry.Id);
-
-                statement.TryBind("@Name", entry.Name);
-                statement.TryBind("@Overview", entry.Overview);
-                statement.TryBind("@ShortOverview", entry.ShortOverview);
-                statement.TryBind("@Type", entry.Type);
-                statement.TryBind("@ItemId", entry.ItemId);
-
-                if (entry.UserId.Equals(Guid.Empty))
-                {
-                    statement.TryBindNull("@UserId");
-                }
-                else
-                {
-                    statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
-                }
-
-                statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
-                statement.TryBind("@LogSeverity", entry.Severity.ToString());
-
-                statement.MoveNext();
-            }, TransactionMode);
-        }
-
-        /// <inheritdoc />
-        public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
-        {
-            var commandText = BaseActivitySelectText;
-            var whereClauses = new List<string>();
-
-            if (minDate.HasValue)
-            {
-                whereClauses.Add("DateCreated>=@DateCreated");
-            }
-
-            if (hasUserId.HasValue)
-            {
-                whereClauses.Add(hasUserId.Value ? "UserId not null" : "UserId is null");
-            }
-
-            var whereTextWithoutPaging = whereClauses.Count == 0 ?
-              string.Empty :
-              " where " + string.Join(" AND ", whereClauses.ToArray());
-
-            if (startIndex.HasValue && startIndex.Value > 0)
-            {
-                var pagingWhereText = whereClauses.Count == 0 ?
-                    string.Empty :
-                    " where " + string.Join(" AND ", whereClauses.ToArray());
-
-                whereClauses.Add(
-                    string.Format(
-                        CultureInfo.InvariantCulture,
-                        "Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
-                        pagingWhereText,
-                        startIndex.Value));
-            }
-
-            var whereText = whereClauses.Count == 0 ?
-                string.Empty :
-                " where " + string.Join(" AND ", whereClauses.ToArray());
-
-            commandText += whereText;
-
-            commandText += " ORDER BY DateCreated DESC";
-
-            if (limit.HasValue)
-            {
-                commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
-            }
-
-            var statementTexts = new[]
-            {
-                commandText,
-                "select count (Id) from ActivityLog" + whereTextWithoutPaging
-            };
-
-            var list = new List<ActivityLogEntry>();
-            var result = new QueryResult<ActivityLogEntry>();
-
-            using var connection = GetConnection(true);
-            connection.RunInTransaction(
-                db =>
-                {
-                    var statements = PrepareAll(db, statementTexts).ToList();
-
-                    using (var statement = statements[0])
-                    {
-                        if (minDate.HasValue)
-                        {
-                            statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
-                        }
-
-                        list.AddRange(statement.ExecuteQuery().Select(GetEntry));
-                    }
-
-                    using (var statement = statements[1])
-                    {
-                        if (minDate.HasValue)
-                        {
-                            statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
-                        }
-
-                        result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
-                    }
-                },
-                ReadTransactionMode);
-
-            result.Items = list;
-            return result;
-        }
-
-        private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)
-        {
-            var index = 0;
-
-            var info = new ActivityLogEntry
-            {
-                Id = reader[index].ToInt64()
-            };
-
-            index++;
-            if (reader[index].SQLiteType != SQLiteType.Null)
-            {
-                info.Name = reader[index].ToString();
-            }
-
-            index++;
-            if (reader[index].SQLiteType != SQLiteType.Null)
-            {
-                info.Overview = reader[index].ToString();
-            }
-
-            index++;
-            if (reader[index].SQLiteType != SQLiteType.Null)
-            {
-                info.ShortOverview = reader[index].ToString();
-            }
-
-            index++;
-            if (reader[index].SQLiteType != SQLiteType.Null)
-            {
-                info.Type = reader[index].ToString();
-            }
-
-            index++;
-            if (reader[index].SQLiteType != SQLiteType.Null)
-            {
-                info.ItemId = reader[index].ToString();
-            }
-
-            index++;
-            if (reader[index].SQLiteType != SQLiteType.Null)
-            {
-                info.UserId = new Guid(reader[index].ToString());
-            }
-
-            index++;
-            info.Date = reader[index].ReadDateTime();
-
-            index++;
-            if (reader[index].SQLiteType != SQLiteType.Null)
-            {
-                info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
-            }
-
-            return info;
-        }
-    }
-}

+ 10 - 4
Emby.Server.Implementations/ApplicationHost.cs

@@ -22,7 +22,6 @@ using Emby.Dlna.Ssdp;
 using Emby.Drawing;
 using Emby.Drawing;
 using Emby.Notifications;
 using Emby.Notifications;
 using Emby.Photos;
 using Emby.Photos;
-using Emby.Server.Implementations.Activity;
 using Emby.Server.Implementations.Archiving;
 using Emby.Server.Implementations.Archiving;
 using Emby.Server.Implementations.Channels;
 using Emby.Server.Implementations.Channels;
 using Emby.Server.Implementations.Collections;
 using Emby.Server.Implementations.Collections;
@@ -47,6 +46,8 @@ using Emby.Server.Implementations.Session;
 using Emby.Server.Implementations.SocketSharp;
 using Emby.Server.Implementations.SocketSharp;
 using Emby.Server.Implementations.TV;
 using Emby.Server.Implementations.TV;
 using Emby.Server.Implementations.Updates;
 using Emby.Server.Implementations.Updates;
+using Jellyfin.Server.Implementations;
+using Jellyfin.Server.Implementations.Activity;
 using MediaBrowser.Api;
 using MediaBrowser.Api;
 using MediaBrowser.Common;
 using MediaBrowser.Common;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
@@ -94,7 +95,6 @@ using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
-using MediaBrowser.Model.Updates;
 using MediaBrowser.Providers.Chapters;
 using MediaBrowser.Providers.Chapters;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Plugins.TheTvdb;
 using MediaBrowser.Providers.Plugins.TheTvdb;
@@ -103,6 +103,7 @@ using MediaBrowser.WebDashboard.Api;
 using MediaBrowser.XbmcMetadata.Providers;
 using MediaBrowser.XbmcMetadata.Providers;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Extensions;
 using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
 using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
@@ -553,6 +554,13 @@ namespace Emby.Server.Implementations
                 return Logger;
                 return Logger;
             });
             });
 
 
+            // TODO: properly set up scoping and switch to AddDbContextPool
+            serviceCollection.AddDbContext<JellyfinDb>(
+                options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
+                ServiceLifetime.Transient);
+
+            serviceCollection.AddSingleton<JellyfinDbProvider>();
+
             serviceCollection.AddSingleton(_fileSystemManager);
             serviceCollection.AddSingleton(_fileSystemManager);
             serviceCollection.AddSingleton<TvdbClientManager>();
             serviceCollection.AddSingleton<TvdbClientManager>();
 
 
@@ -663,7 +671,6 @@ namespace Emby.Server.Implementations
 
 
             serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
             serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
 
 
-            serviceCollection.AddSingleton<IActivityRepository, ActivityRepository>();
             serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
             serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
 
 
             serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
             serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
@@ -696,7 +703,6 @@ namespace Emby.Server.Implementations
             ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
             ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
             ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
             ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
             ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
             ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
-            ((ActivityRepository)Resolve<IActivityRepository>()).Initialize();
 
 
             SetStaticProperties();
             SetStaticProperties();
 
 

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

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
 
   <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
   <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
   <PropertyGroup>
   <PropertyGroup>
@@ -9,6 +9,7 @@
     <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
     <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
     <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
     <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
     <ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
     <ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
+    <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -50,7 +51,7 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
   </PropertyGroup>

+ 0 - 1140
Jellyfin.Data/DbContexts/Jellyfin.cs

@@ -1,1140 +0,0 @@
-//------------------------------------------------------------------------------
-// <auto-generated>
-//     This code was generated from a template.
-//
-//     Manual changes to this file may cause unexpected behavior in your application.
-//     Manual changes to this file will be overwritten if the code is regenerated.
-//
-//     Produced by Entity Framework Visual Editor
-//     https://github.com/msawczyn/EFDesigner
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.ComponentModel.DataAnnotations.Schema;
-using Microsoft.EntityFrameworkCore;
-
-namespace Jellyfin.Data.DbContexts
-{
-   /// <inheritdoc/>
-   public partial class Jellyfin : DbContext
-   {
-      #region DbSets
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Artwork> Artwork { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Book> Books { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.BookMetadata> BookMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Chapter> Chapters { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Collection> Collections { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.CollectionItem> CollectionItems { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Company> Companies { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.CompanyMetadata> CompanyMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.CustomItem> CustomItems { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.CustomItemMetadata> CustomItemMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Episode> Episodes { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.EpisodeMetadata> EpisodeMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Genre> Genres { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Group> Groups { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Library> Libraries { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.LibraryItem> LibraryItems { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.LibraryRoot> LibraryRoot { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MediaFile> MediaFiles { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MediaFileStream> MediaFileStream { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Metadata> Metadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MetadataProvider> MetadataProviders { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MetadataProviderId> MetadataProviderIds { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Movie> Movies { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MovieMetadata> MovieMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MusicAlbum> MusicAlbums { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Permission> Permissions { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Person> People { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.PersonRole> PersonRoles { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Photo> Photo { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.PhotoMetadata> PhotoMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Preference> Preferences { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.ProviderMapping> ProviderMappings { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Rating> Ratings { get; set; }
-
-      /// <summary>
-      /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to
-      /// store review ratings, not age ratings
-      /// </summary>
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.RatingSource> RatingSources { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Release> Releases { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Season> Seasons { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.SeasonMetadata> SeasonMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Series> Series { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.SeriesMetadata> SeriesMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Track> Tracks { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.TrackMetadata> TrackMetadata { get; set; }
-      public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.User> Users { get; set; }
-      #endregion DbSets
-
-      /// <summary>
-      /// Default connection string
-      /// </summary>
-      public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db";
-
-      /// <inheritdoc />
-      public Jellyfin(DbContextOptions<Jellyfin> options) : base(options)
-      {
-      }
-
-      partial void CustomInit(DbContextOptionsBuilder optionsBuilder);
-
-      /// <inheritdoc />
-      protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
-      {
-         CustomInit(optionsBuilder);
-      }
-
-      partial void OnModelCreatingImpl(ModelBuilder modelBuilder);
-      partial void OnModelCreatedImpl(ModelBuilder modelBuilder);
-
-      /// <inheritdoc />
-      protected override void OnModelCreating(ModelBuilder modelBuilder)
-      {
-         base.OnModelCreating(modelBuilder);
-         OnModelCreatingImpl(modelBuilder);
-
-         modelBuilder.HasDefaultSchema("jellyfin");
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
-                     .ToTable("Artwork")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
-                     .Property(t => t.Path)
-                     .HasMaxLength(65535)
-                     .IsRequired()
-                     .HasField("_Path")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
-                     .Property(t => t.Kind)
-                     .IsRequired()
-                     .HasField("_Kind")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>().HasIndex(t => t.Kind);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Book>()
-                     .HasMany(x => x.BookMetadata)
-                     .WithOne()
-                     .HasForeignKey("BookMetadata_BookMetadata_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Book>()
-                     .HasMany(x => x.Releases)
-                     .WithOne()
-                     .HasForeignKey("Release_Releases_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.BookMetadata>()
-                     .Property(t => t.ISBN)
-                     .HasField("_ISBN")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.BookMetadata>()
-                     .HasMany(x => x.Publishers)
-                     .WithOne()
-                     .HasForeignKey("Company_Publishers_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
-                     .ToTable("Chapter")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(1024)
-                     .HasField("_Name")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
-                     .Property(t => t.Language)
-                     .HasMaxLength(3)
-                     .IsRequired()
-                     .HasField("_Language")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
-                     .Property(t => t.TimeStart)
-                     .IsRequired()
-                     .HasField("_TimeStart")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
-                     .Property(t => t.TimeEnd)
-                     .HasField("_TimeEnd")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
-                     .ToTable("Collection")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(1024)
-                     .HasField("_Name")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
-                     .HasMany(x => x.CollectionItem)
-                     .WithOne()
-                     .HasForeignKey("CollectionItem_CollectionItem_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
-                     .ToTable("CollectionItem")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
-                     .HasOne(x => x.LibraryItem)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.CollectionItem>("LibraryItem_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
-                     .HasOne(x => x.Next)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.CollectionItem>("CollectionItem_Next_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
-                     .HasOne(x => x.Previous)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.CollectionItem>("CollectionItem_Previous_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
-                     .ToTable("Company")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
-                     .HasMany(x => x.CompanyMetadata)
-                     .WithOne()
-                     .HasForeignKey("CompanyMetadata_CompanyMetadata_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
-                     .HasOne(x => x.Parent)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.Company>("Company_Parent_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CompanyMetadata>()
-                     .Property(t => t.Description)
-                     .HasMaxLength(65535)
-                     .HasField("_Description")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CompanyMetadata>()
-                     .Property(t => t.Headquarters)
-                     .HasMaxLength(255)
-                     .HasField("_Headquarters")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CompanyMetadata>()
-                     .Property(t => t.Country)
-                     .HasMaxLength(2)
-                     .HasField("_Country")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CompanyMetadata>()
-                     .Property(t => t.Homepage)
-                     .HasMaxLength(1024)
-                     .HasField("_Homepage")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CustomItem>()
-                     .HasMany(x => x.CustomItemMetadata)
-                     .WithOne()
-                     .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.CustomItem>()
-                     .HasMany(x => x.Releases)
-                     .WithOne()
-                     .HasForeignKey("Release_Releases_Id")
-                     .IsRequired();
-
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Episode>()
-                     .Property(t => t.EpisodeNumber)
-                     .HasField("_EpisodeNumber")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Episode>()
-                     .HasMany(x => x.Releases)
-                     .WithOne()
-                     .HasForeignKey("Release_Releases_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Episode>()
-                     .HasMany(x => x.EpisodeMetadata)
-                     .WithOne()
-                     .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.EpisodeMetadata>()
-                     .Property(t => t.Outline)
-                     .HasMaxLength(1024)
-                     .HasField("_Outline")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.EpisodeMetadata>()
-                     .Property(t => t.Plot)
-                     .HasMaxLength(65535)
-                     .HasField("_Plot")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.EpisodeMetadata>()
-                     .Property(t => t.Tagline)
-                     .HasMaxLength(1024)
-                     .HasField("_Tagline")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>()
-                     .ToTable("Genre")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(255)
-                     .IsRequired()
-                     .HasField("_Name")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>().HasIndex(t => t.Name)
-                     .IsUnique();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
-                     .ToTable("Groups")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(255)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>().Property<byte[]>("Timestamp").IsConcurrencyToken();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
-                     .HasMany(x => x.GroupPermissions)
-                     .WithOne()
-                     .HasForeignKey("Permission_GroupPermissions_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
-                     .HasMany(x => x.ProviderMappings)
-                     .WithOne()
-                     .HasForeignKey("ProviderMapping_ProviderMappings_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
-                     .HasMany(x => x.Preferences)
-                     .WithOne()
-                     .HasForeignKey("Preference_Preferences_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Library>()
-                     .ToTable("Library")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Library>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Library>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(1024)
-                     .IsRequired()
-                     .HasField("_Name")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Library>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
-                     .ToTable("LibraryItem")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
-                     .Property(t => t.UrlId)
-                     .IsRequired()
-                     .HasField("_UrlId")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>().HasIndex(t => t.UrlId)
-                     .IsUnique();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
-                     .Property(t => t.DateAdded)
-                     .IsRequired()
-                     .HasField("_DateAdded")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
-                     .HasOne(x => x.LibraryRoot)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.LibraryItem>("LibraryRoot_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
-                     .ToTable("LibraryRoot")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
-                     .Property(t => t.Path)
-                     .HasMaxLength(65535)
-                     .IsRequired()
-                     .HasField("_Path")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
-                     .Property(t => t.NetworkPath)
-                     .HasMaxLength(65535)
-                     .HasField("_NetworkPath")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
-                     .HasOne(x => x.Library)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.LibraryRoot>("Library_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
-                     .ToTable("MediaFile")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
-                     .Property(t => t.Path)
-                     .HasMaxLength(65535)
-                     .IsRequired()
-                     .HasField("_Path")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
-                     .Property(t => t.Kind)
-                     .IsRequired()
-                     .HasField("_Kind")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
-                     .HasMany(x => x.MediaFileStreams)
-                     .WithOne()
-                     .HasForeignKey("MediaFileStream_MediaFileStreams_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFileStream>()
-                     .ToTable("MediaFileStream")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFileStream>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFileStream>()
-                     .Property(t => t.StreamNumber)
-                     .IsRequired()
-                     .HasField("_StreamNumber")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFileStream>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .ToTable("Metadata")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.Title)
-                     .HasMaxLength(1024)
-                     .IsRequired()
-                     .HasField("_Title")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.OriginalTitle)
-                     .HasMaxLength(1024)
-                     .HasField("_OriginalTitle")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.SortTitle)
-                     .HasMaxLength(1024)
-                     .HasField("_SortTitle")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.Language)
-                     .HasMaxLength(3)
-                     .IsRequired()
-                     .HasField("_Language")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.ReleaseDate)
-                     .HasField("_ReleaseDate")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.DateAdded)
-                     .IsRequired()
-                     .HasField("_DateAdded")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.DateModified)
-                     .IsRequired()
-                     .HasField("_DateModified")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .HasMany(x => x.PersonRoles)
-                     .WithOne()
-                     .HasForeignKey("PersonRole_PersonRoles_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .HasMany(x => x.Genres)
-                     .WithOne()
-                     .HasForeignKey("Genre_Genres_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .HasMany(x => x.Artwork)
-                     .WithOne()
-                     .HasForeignKey("Artwork_Artwork_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .HasMany(x => x.Ratings)
-                     .WithOne()
-                     .HasForeignKey("Rating_Ratings_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
-                     .HasMany(x => x.Sources)
-                     .WithOne()
-                     .HasForeignKey("MetadataProviderId_Sources_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProvider>()
-                     .ToTable("MetadataProvider")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProvider>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProvider>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(1024)
-                     .IsRequired()
-                     .HasField("_Name")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProvider>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
-                     .ToTable("MetadataProviderId")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
-                     .Property(t => t.ProviderId)
-                     .HasMaxLength(255)
-                     .IsRequired()
-                     .HasField("_ProviderId")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
-                     .HasOne(x => x.MetadataProvider)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.MetadataProviderId>("MetadataProvider_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Movie>()
-                     .HasMany(x => x.Releases)
-                     .WithOne()
-                     .HasForeignKey("Release_Releases_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Movie>()
-                     .HasMany(x => x.MovieMetadata)
-                     .WithOne()
-                     .HasForeignKey("MovieMetadata_MovieMetadata_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
-                     .Property(t => t.Outline)
-                     .HasMaxLength(1024)
-                     .HasField("_Outline")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
-                     .Property(t => t.Plot)
-                     .HasMaxLength(65535)
-                     .HasField("_Plot")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
-                     .Property(t => t.Tagline)
-                     .HasMaxLength(1024)
-                     .HasField("_Tagline")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
-                     .Property(t => t.Country)
-                     .HasMaxLength(2)
-                     .HasField("_Country")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
-                     .HasMany(x => x.Studios)
-                     .WithOne()
-                     .HasForeignKey("Company_Studios_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbum>()
-                     .HasMany(x => x.MusicAlbumMetadata)
-                     .WithOne()
-                     .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbum>()
-                     .HasMany(x => x.Tracks)
-                     .WithOne()
-                     .HasForeignKey("Track_Tracks_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbumMetadata>()
-                     .Property(t => t.Barcode)
-                     .HasMaxLength(255)
-                     .HasField("_Barcode")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbumMetadata>()
-                     .Property(t => t.LabelNumber)
-                     .HasMaxLength(255)
-                     .HasField("_LabelNumber")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbumMetadata>()
-                     .Property(t => t.Country)
-                     .HasMaxLength(2)
-                     .HasField("_Country")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbumMetadata>()
-                     .HasMany(x => x.Labels)
-                     .WithOne()
-                     .HasForeignKey("Company_Labels_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>()
-                     .ToTable("Permissions")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>()
-                     .Property(t => t.Kind)
-                     .IsRequired()
-                     .HasField("_Kind")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>()
-                     .Property(t => t.Value)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>().Property<byte[]>("Timestamp").IsConcurrencyToken();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .ToTable("Person")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .Property(t => t.UrlId)
-                     .IsRequired()
-                     .HasField("_UrlId")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(1024)
-                     .IsRequired()
-                     .HasField("_Name")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .Property(t => t.SourceId)
-                     .HasMaxLength(255)
-                     .HasField("_SourceId")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .Property(t => t.DateAdded)
-                     .IsRequired()
-                     .HasField("_DateAdded")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .Property(t => t.DateModified)
-                     .IsRequired()
-                     .HasField("_DateModified")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
-                     .HasMany(x => x.Sources)
-                     .WithOne()
-                     .HasForeignKey("MetadataProviderId_Sources_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
-                     .ToTable("PersonRole")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
-                     .Property(t => t.Role)
-                     .HasMaxLength(1024)
-                     .HasField("_Role")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
-                     .Property(t => t.Type)
-                     .IsRequired()
-                     .HasField("_Type")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
-                     .HasOne(x => x.Person)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.PersonRole>("Person_Id")
-                     .IsRequired()
-                     .OnDelete(DeleteBehavior.Cascade);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
-                     .HasOne(x => x.Artwork)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.PersonRole>("Artwork_Artwork_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
-                     .HasMany(x => x.Sources)
-                     .WithOne()
-                     .HasForeignKey("MetadataProviderId_Sources_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Photo>()
-                     .HasMany(x => x.PhotoMetadata)
-                     .WithOne()
-                     .HasForeignKey("PhotoMetadata_PhotoMetadata_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Photo>()
-                     .HasMany(x => x.Releases)
-                     .WithOne()
-                     .HasForeignKey("Release_Releases_Id")
-                     .IsRequired();
-
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>()
-                     .ToTable("Preferences")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>()
-                     .Property(t => t.Kind)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>()
-                     .Property(t => t.Value)
-                     .HasMaxLength(65535)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>().Property<byte[]>("Timestamp").IsConcurrencyToken();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
-                     .ToTable("ProviderMappings")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
-                     .Property(t => t.ProviderName)
-                     .HasMaxLength(255)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
-                     .Property(t => t.ProviderSecrets)
-                     .HasMaxLength(65535)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
-                     .Property(t => t.ProviderData)
-                     .HasMaxLength(65535)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>().Property<byte[]>("Timestamp").IsConcurrencyToken();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
-                     .ToTable("Rating")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
-                     .Property(t => t.Value)
-                     .IsRequired()
-                     .HasField("_Value")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
-                     .Property(t => t.Votes)
-                     .HasField("_Votes")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
-                     .HasOne(x => x.RatingType)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.Rating>("RatingSource_RatingType_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
-                     .ToTable("RatingType")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(1024)
-                     .HasField("_Name")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
-                     .Property(t => t.MaximumValue)
-                     .IsRequired()
-                     .HasField("_MaximumValue")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
-                     .Property(t => t.MinimumValue)
-                     .IsRequired()
-                     .HasField("_MinimumValue")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
-                     .HasOne(x => x.Source)
-                     .WithOne()
-                     .HasForeignKey<global::Jellyfin.Data.Entities.RatingSource>("MetadataProviderId_Source_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
-                     .ToTable("Release")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .HasField("_Id")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
-                     .Property(t => t.Name)
-                     .HasMaxLength(1024)
-                     .IsRequired()
-                     .HasField("_Name")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
-                     .Property(t => t.Timestamp)
-                     .IsRequired()
-                     .HasField("_Timestamp")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property)
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
-                     .HasMany(x => x.MediaFiles)
-                     .WithOne()
-                     .HasForeignKey("MediaFile_MediaFiles_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
-                     .HasMany(x => x.Chapters)
-                     .WithOne()
-                     .HasForeignKey("Chapter_Chapters_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Season>()
-                     .Property(t => t.SeasonNumber)
-                     .HasField("_SeasonNumber")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Season>()
-                     .HasMany(x => x.SeasonMetadata)
-                     .WithOne()
-                     .HasForeignKey("SeasonMetadata_SeasonMetadata_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Season>()
-                     .HasMany(x => x.Episodes)
-                     .WithOne()
-                     .HasForeignKey("Episode_Episodes_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.SeasonMetadata>()
-                     .Property(t => t.Outline)
-                     .HasMaxLength(1024)
-                     .HasField("_Outline")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
-                     .Property(t => t.AirsDayOfWeek)
-                     .HasField("_AirsDayOfWeek")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
-                     .Property(t => t.AirsTime)
-                     .HasField("_AirsTime")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
-                     .Property(t => t.FirstAired)
-                     .HasField("_FirstAired")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
-                     .HasMany(x => x.SeriesMetadata)
-                     .WithOne()
-                     .HasForeignKey("SeriesMetadata_SeriesMetadata_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
-                     .HasMany(x => x.Seasons)
-                     .WithOne()
-                     .HasForeignKey("Season_Seasons_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
-                     .Property(t => t.Outline)
-                     .HasMaxLength(1024)
-                     .HasField("_Outline")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
-                     .Property(t => t.Plot)
-                     .HasMaxLength(65535)
-                     .HasField("_Plot")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
-                     .Property(t => t.Tagline)
-                     .HasMaxLength(1024)
-                     .HasField("_Tagline")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
-                     .Property(t => t.Country)
-                     .HasMaxLength(2)
-                     .HasField("_Country")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
-                     .HasMany(x => x.Networks)
-                     .WithOne()
-                     .HasForeignKey("Company_Networks_Id")
-                     .IsRequired();
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Track>()
-                     .Property(t => t.TrackNumber)
-                     .HasField("_TrackNumber")
-                     .UsePropertyAccessMode(PropertyAccessMode.Property);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Track>()
-                     .HasMany(x => x.Releases)
-                     .WithOne()
-                     .HasForeignKey("Release_Releases_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.Track>()
-                     .HasMany(x => x.TrackMetadata)
-                     .WithOne()
-                     .HasForeignKey("TrackMetadata_TrackMetadata_Id")
-                     .IsRequired();
-
-
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .ToTable("Users")
-                     .HasKey(t => t.Id);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.Id)
-                     .IsRequired()
-                     .ValueGeneratedOnAdd();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.LastLoginTimestamp)
-                     .IsRequired()
-                     .IsRowVersion();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.Username)
-                     .HasMaxLength(255)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.Password)
-                     .HasMaxLength(65535);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.MustUpdatePassword)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.AudioLanguagePreference)
-                     .HasMaxLength(255)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.AuthenticationProviderId)
-                     .HasMaxLength(255)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.GroupedFolders)
-                     .HasMaxLength(65535);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.InvalidLoginAttemptCount)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.LatestItemExcludes)
-                     .HasMaxLength(65535);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.MyMediaExcludes)
-                     .HasMaxLength(65535);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.OrderedViews)
-                     .HasMaxLength(65535);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.SubtitleMode)
-                     .HasMaxLength(255)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.PlayDefaultAudioTrack)
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .Property(t => t.SubtitleLanguagePrefernce)
-                     .HasMaxLength(255);
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .HasMany(x => x.Groups)
-                     .WithOne()
-                     .HasForeignKey("Group_Groups_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .HasMany(x => x.Permissions)
-                     .WithOne()
-                     .HasForeignKey("Permission_Permissions_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .HasMany(x => x.ProviderMappings)
-                     .WithOne()
-                     .HasForeignKey("ProviderMapping_ProviderMappings_Id")
-                     .IsRequired();
-         modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
-                     .HasMany(x => x.Preferences)
-                     .WithOne()
-                     .HasForeignKey("Preference_Preferences_Id")
-                     .IsRequired();
-
-         OnModelCreatedImpl(modelBuilder);
-      }
-   }
-}

+ 153 - 0
Jellyfin.Data/Entities/ActivityLog.cs

@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+    [Table("ActivityLog")]
+    public partial class ActivityLog
+    {
+        partial void Init();
+
+        /// <summary>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </summary>
+        protected ActivityLog()
+        {
+            Init();
+        }
+
+        /// <summary>
+        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+        /// </summary>
+        public static ActivityLog CreateActivityLogUnsafe()
+        {
+            return new ActivityLog();
+        }
+
+        /// <summary>
+        /// Public constructor with required data
+        /// </summary>
+        /// <param name="name"></param>
+        /// <param name="type"></param>
+        /// <param name="userid"></param>
+        /// <param name="datecreated"></param>
+        /// <param name="logseverity"></param>
+        public ActivityLog(string name, string type, Guid userid, DateTime datecreated, Microsoft.Extensions.Logging.LogLevel logseverity)
+        {
+            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+            this.Name = name;
+
+            if (string.IsNullOrEmpty(type)) throw new ArgumentNullException(nameof(type));
+            this.Type = type;
+
+            this.UserId = userid;
+
+            this.DateCreated = datecreated;
+
+            this.LogSeverity = logseverity;
+
+
+            Init();
+        }
+
+        /// <summary>
+        /// Static create function (for use in LINQ queries, etc.)
+        /// </summary>
+        /// <param name="name"></param>
+        /// <param name="type"></param>
+        /// <param name="userid"></param>
+        /// <param name="datecreated"></param>
+        /// <param name="logseverity"></param>
+        public static ActivityLog Create(string name, string type, Guid userid, DateTime datecreated, Microsoft.Extensions.Logging.LogLevel logseverity)
+        {
+            return new ActivityLog(name, type, userid, datecreated, logseverity);
+        }
+
+        /*************************************************************************
+         * Properties
+         *************************************************************************/
+
+        /// <summary>
+        /// Identity, Indexed, Required
+        /// </summary>
+        [Key]
+        [Required]
+        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        public int Id { get; protected set; }
+
+        /// <summary>
+        /// Required, Max length = 512
+        /// </summary>
+        [Required]
+        [MaxLength(512)]
+        [StringLength(512)]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Max length = 512
+        /// </summary>
+        [MaxLength(512)]
+        [StringLength(512)]
+        public string Overview { get; set; }
+
+        /// <summary>
+        /// Max length = 512
+        /// </summary>
+        [MaxLength(512)]
+        [StringLength(512)]
+        public string ShortOverview { get; set; }
+
+        /// <summary>
+        /// Required, Max length = 256
+        /// </summary>
+        [Required]
+        [MaxLength(256)]
+        [StringLength(256)]
+        public string Type { get; set; }
+
+        /// <summary>
+        /// Required
+        /// </summary>
+        [Required]
+        public Guid UserId { get; set; }
+
+        /// <summary>
+        /// Max length = 256
+        /// </summary>
+        [MaxLength(256)]
+        [StringLength(256)]
+        public string ItemId { get; set; }
+
+        /// <summary>
+        /// Required
+        /// </summary>
+        [Required]
+        public DateTime DateCreated { get; set; }
+
+        /// <summary>
+        /// Required
+        /// </summary>
+        [Required]
+        public Microsoft.Extensions.Logging.LogLevel LogSeverity { get; set; }
+
+        /// <summary>
+        /// Required, ConcurrenyToken
+        /// </summary>
+        [ConcurrencyCheck]
+        [Required]
+        public uint RowVersion { get; set; }
+
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
+
+    }
+}
+

+ 9 - 0
Jellyfin.Data/ISavingChanges.cs

@@ -0,0 +1,9 @@
+#pragma warning disable CS1591
+
+namespace Jellyfin.Data
+{
+    public interface ISavingChanges
+    {
+        void OnSavingChanges();
+    }
+}

+ 21 - 3
Jellyfin.Data/Jellyfin.Data.csproj

@@ -1,12 +1,30 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
 
 
+  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+
+  <!-- Code analysers-->
+  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+  </ItemGroup>
+  
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.2.4" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.3" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
   </ItemGroup>
   </ItemGroup>
 
 
 </Project>
 </Project>

+ 103 - 0
Jellyfin.Server.Implementations/Activity/ActivityManager.cs

@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using MediaBrowser.Model.Activity;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Querying;
+
+namespace Jellyfin.Server.Implementations.Activity
+{
+    /// <summary>
+    /// Manages the storage and retrieval of <see cref="ActivityLog"/> instances.
+    /// </summary>
+    public class ActivityManager : IActivityManager
+    {
+        private JellyfinDbProvider _provider;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ActivityManager"/> class.
+        /// </summary>
+        /// <param name="provider">The Jellyfin database provider.</param>
+        public ActivityManager(JellyfinDbProvider provider)
+        {
+            _provider = provider;
+        }
+
+        /// <inheritdoc/>
+        public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
+
+        /// <inheritdoc/>
+        public void Create(ActivityLog entry)
+        {
+            using var dbContext = _provider.CreateContext();
+            dbContext.ActivityLogs.Add(entry);
+            dbContext.SaveChanges();
+
+            EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
+        }
+
+        /// <inheritdoc/>
+        public async Task CreateAsync(ActivityLog entry)
+        {
+            using var dbContext = _provider.CreateContext();
+            await dbContext.ActivityLogs.AddAsync(entry);
+            await dbContext.SaveChangesAsync().ConfigureAwait(false);
+
+            EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
+        }
+
+        /// <inheritdoc/>
+        public QueryResult<ActivityLogEntry> GetPagedResult(
+            Func<IQueryable<ActivityLog>, IEnumerable<ActivityLog>> func,
+            int? startIndex,
+            int? limit)
+        {
+            using var dbContext = _provider.CreateContext();
+
+            var result = func.Invoke(dbContext.ActivityLogs).AsQueryable();
+
+            if (startIndex.HasValue)
+            {
+                result = result.Where(entry => entry.Id >= startIndex.Value);
+            }
+
+            if (limit.HasValue)
+            {
+                result = result.OrderByDescending(entry => entry.DateCreated).Take(limit.Value);
+            }
+
+            // This converts the objects from the new database model to the old for compatibility with the existing API.
+            var list = result.Select(entry => ConvertToOldModel(entry)).ToList();
+
+            return new QueryResult<ActivityLogEntry>()
+            {
+                Items = list,
+                TotalRecordCount = list.Count
+            };
+        }
+
+        /// <inheritdoc/>
+        public QueryResult<ActivityLogEntry> GetPagedResult(int? startIndex, int? limit)
+        {
+            return GetPagedResult(logs => logs, startIndex, limit);
+        }
+
+        private static ActivityLogEntry ConvertToOldModel(ActivityLog entry)
+        {
+            return new ActivityLogEntry
+            {
+                Id = entry.Id,
+                Name = entry.Name,
+                Overview = entry.Overview,
+                ShortOverview = entry.ShortOverview,
+                Type = entry.Type,
+                ItemId = entry.ItemId,
+                UserId = entry.UserId,
+                Date = entry.DateCreated,
+                Severity = entry.LogSeverity
+            };
+        }
+    }
+}

+ 34 - 0
Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj

@@ -0,0 +1,34 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+
+  <!-- Code analysers-->
+  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="..\SharedVersion.cs" />
+    <Compile Remove="Migrations\20200430214405_InitialSchema.cs" />
+    <Compile Remove="Migrations\20200430214405_InitialSchema.Designer.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" />
+    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
+    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
+  </ItemGroup>
+
+</Project>

+ 119 - 0
Jellyfin.Server.Implementations/JellyfinDb.cs

@@ -0,0 +1,119 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1201 // Constuctors should not follow properties
+#pragma warning disable SA1516 // Elements should be followed by a blank line
+#pragma warning disable SA1623 // Property's documentation should begin with gets or sets
+#pragma warning disable SA1629 // Documentation should end with a period
+#pragma warning disable SA1648 // Inheritdoc should be used with inheriting class
+
+using System.Linq;
+using Jellyfin.Data;
+using Jellyfin.Data.Entities;
+using Microsoft.EntityFrameworkCore;
+
+namespace Jellyfin.Server.Implementations
+{
+    /// <inheritdoc/>
+    public partial class JellyfinDb : DbContext
+    {
+        public virtual DbSet<ActivityLog> ActivityLogs { get; set; }
+        public virtual DbSet<Artwork> Artwork { get; set; }
+        public virtual DbSet<Book> Books { get; set; }
+        public virtual DbSet<BookMetadata> BookMetadata { get; set; }
+        public virtual DbSet<Chapter> Chapters { get; set; }
+        public virtual DbSet<Collection> Collections { get; set; }
+        public virtual DbSet<CollectionItem> CollectionItems { get; set; }
+        public virtual DbSet<Company> Companies { get; set; }
+        public virtual DbSet<CompanyMetadata> CompanyMetadata { get; set; }
+        public virtual DbSet<CustomItem> CustomItems { get; set; }
+        public virtual DbSet<CustomItemMetadata> CustomItemMetadata { get; set; }
+        public virtual DbSet<Episode> Episodes { get; set; }
+        public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; }
+        public virtual DbSet<Genre> Genres { get; set; }
+        public virtual DbSet<Group> Groups { get; set; }
+        public virtual DbSet<Library> Libraries { get; set; }
+        public virtual DbSet<LibraryItem> LibraryItems { get; set; }
+        public virtual DbSet<LibraryRoot> LibraryRoot { get; set; }
+        public virtual DbSet<MediaFile> MediaFiles { get; set; }
+        public virtual DbSet<MediaFileStream> MediaFileStream { get; set; }
+        public virtual DbSet<Metadata> Metadata { get; set; }
+        public virtual DbSet<MetadataProvider> MetadataProviders { get; set; }
+        public virtual DbSet<MetadataProviderId> MetadataProviderIds { get; set; }
+        public virtual DbSet<Movie> Movies { get; set; }
+        public virtual DbSet<MovieMetadata> MovieMetadata { get; set; }
+        public virtual DbSet<MusicAlbum> MusicAlbums { get; set; }
+        public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
+        public virtual DbSet<Permission> Permissions { get; set; }
+        public virtual DbSet<Person> People { get; set; }
+        public virtual DbSet<PersonRole> PersonRoles { get; set; }
+        public virtual DbSet<Photo> Photo { get; set; }
+        public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; }
+        public virtual DbSet<Preference> Preferences { get; set; }
+        public virtual DbSet<ProviderMapping> ProviderMappings { get; set; }
+        public virtual DbSet<Rating> Ratings { get; set; }
+
+        /// <summary>
+        /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to
+        /// store review ratings, not age ratings
+        /// </summary>
+        public virtual DbSet<RatingSource> RatingSources { get; set; }
+        public virtual DbSet<Release> Releases { get; set; }
+        public virtual DbSet<Season> Seasons { get; set; }
+        public virtual DbSet<SeasonMetadata> SeasonMetadata { get; set; }
+        public virtual DbSet<Series> Series { get; set; }
+        public virtual DbSet<SeriesMetadata> SeriesMetadata { get; set; }
+        public virtual DbSet<Track> Tracks { get; set; }
+        public virtual DbSet<TrackMetadata> TrackMetadata { get; set; }
+        public virtual DbSet<User> Users { get; set; }
+
+        /// <summary>
+        /// Gets or sets the default connection string.
+        /// </summary>
+        public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db";
+
+        /// <inheritdoc />
+        public JellyfinDb(DbContextOptions<JellyfinDb> options) : base(options)
+        {
+        }
+
+        partial void CustomInit(DbContextOptionsBuilder optionsBuilder);
+
+        /// <inheritdoc />
+        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+        {
+            CustomInit(optionsBuilder);
+        }
+
+        partial void OnModelCreatingImpl(ModelBuilder modelBuilder);
+        partial void OnModelCreatedImpl(ModelBuilder modelBuilder);
+
+        /// <inheritdoc />
+        protected override void OnModelCreating(ModelBuilder modelBuilder)
+        {
+            base.OnModelCreating(modelBuilder);
+            OnModelCreatingImpl(modelBuilder);
+
+            modelBuilder.HasDefaultSchema("jellyfin");
+
+            modelBuilder.Entity<Artwork>().HasIndex(t => t.Kind);
+
+            modelBuilder.Entity<Genre>().HasIndex(t => t.Name)
+                        .IsUnique();
+
+            modelBuilder.Entity<LibraryItem>().HasIndex(t => t.UrlId)
+                        .IsUnique();
+
+            OnModelCreatedImpl(modelBuilder);
+        }
+
+        public override int SaveChanges()
+        {
+            foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified))
+            {
+                var saveEntity = entity.Entity as ISavingChanges;
+                saveEntity.OnSavingChanges();
+            }
+
+            return base.SaveChanges();
+        }
+    }
+}

+ 33 - 0
Jellyfin.Server.Implementations/JellyfinDbProvider.cs

@@ -0,0 +1,33 @@
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Jellyfin.Server.Implementations
+{
+    /// <summary>
+    /// Factory class for generating new <see cref="JellyfinDb"/> instances.
+    /// </summary>
+    public class JellyfinDbProvider
+    {
+        private readonly IServiceProvider _serviceProvider;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="JellyfinDbProvider"/> class.
+        /// </summary>
+        /// <param name="serviceProvider">The application's service provider.</param>
+        public JellyfinDbProvider(IServiceProvider serviceProvider)
+        {
+            _serviceProvider = serviceProvider;
+            serviceProvider.GetService<JellyfinDb>().Database.Migrate();
+        }
+
+        /// <summary>
+        /// Creates a new <see cref="JellyfinDb"/> context.
+        /// </summary>
+        /// <returns>The newly created context.</returns>
+        public JellyfinDb CreateContext()
+        {
+            return _serviceProvider.GetService<JellyfinDb>();
+        }
+    }
+}

+ 1513 - 0
Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs

@@ -0,0 +1,1513 @@
+#pragma warning disable CS1591
+
+// <auto-generated />
+using System;
+using Jellyfin.Server.Implementations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+    [DbContext(typeof(JellyfinDb))]
+    [Migration("20200430215054_InitialSchema")]
+    partial class InitialSchema
+    {
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasDefaultSchema("jellyfin")
+                .HasAnnotation("ProductVersion", "3.1.3");
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateCreated")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("ItemId")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(256);
+
+                    b.Property<int>("LogSeverity")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(512);
+
+                    b.Property<string>("Overview")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(512);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ShortOverview")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(512);
+
+                    b.Property<string>("Type")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(256);
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("ActivityLog");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Kind");
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.ToTable("Artwork");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Chapter_Chapters_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Language")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(3);
+
+                    b.Property<string>("Name")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<long?>("TimeEnd")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<long>("TimeStart")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Chapter_Chapters_Id");
+
+                    b.ToTable("Chapter");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Collection", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Collection");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("CollectionItem_CollectionItem_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("CollectionItem_Next_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("CollectionItem_Previous_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("LibraryItem_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("CollectionItem_CollectionItem_Id");
+
+                    b.HasIndex("CollectionItem_Next_Id");
+
+                    b.HasIndex("CollectionItem_Previous_Id");
+
+                    b.HasIndex("LibraryItem_Id");
+
+                    b.ToTable("CollectionItem");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Company", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Labels_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Networks_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Parent_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Publishers_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Studios_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Company_Labels_Id");
+
+                    b.HasIndex("Company_Networks_Id");
+
+                    b.HasIndex("Company_Parent_Id");
+
+                    b.HasIndex("Company_Publishers_Id");
+
+                    b.HasIndex("Company_Studios_Id");
+
+                    b.ToTable("Company");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Name")
+                        .IsUnique();
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.ToTable("Genre");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Group_Groups_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Group_Groups_Id");
+
+                    b.ToTable("Group");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Library", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Library");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.LibraryItem", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateAdded")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Discriminator")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property<int?>("LibraryRoot_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid>("UrlId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("LibraryRoot_Id");
+
+                    b.HasIndex("UrlId")
+                        .IsUnique();
+
+                    b.ToTable("LibraryItem");
+
+                    b.HasDiscriminator<string>("Discriminator").HasValue("LibraryItem");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Library_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("NetworkPath")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Library_Id");
+
+                    b.ToTable("LibraryRoot");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MediaFile_MediaFiles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("MediaFile_MediaFiles_Id");
+
+                    b.ToTable("MediaFile");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MediaFileStream_MediaFileStreams_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("StreamNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("MediaFileStream_MediaFileStreams_Id");
+
+                    b.ToTable("MediaFileStream");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Metadata", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateAdded")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateModified")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Discriminator")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Language")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(3);
+
+                    b.Property<string>("OriginalTitle")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<DateTimeOffset?>("ReleaseDate")
+                        .HasColumnType("TEXT");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("SortTitle")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<string>("Title")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Metadata");
+
+                    b.HasDiscriminator<string>("Discriminator").HasValue("Metadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProvider", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("MetadataProvider");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MetadataProviderId_Sources_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MetadataProvider_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ProviderId")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("MetadataProviderId_Sources_Id");
+
+                    b.HasIndex("MetadataProvider_Id");
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.ToTable("MetadataProviderId");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Permission_GroupPermissions_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Permission_Permissions_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("Value")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Permission_GroupPermissions_Id");
+
+                    b.HasIndex("Permission_Permissions_Id");
+
+                    b.ToTable("Permission");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Person", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateAdded")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateModified")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("SourceId")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<Guid>("UrlId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Person");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.PersonRole", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Artwork_Artwork_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Person_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Role")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Type")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Artwork_Artwork_Id");
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.HasIndex("Person_Id");
+
+                    b.ToTable("PersonRole");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Preference_Preferences_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Value")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Preference_Preferences_Id");
+
+                    b.ToTable("Preference");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ProviderData")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<int?>("ProviderMapping_ProviderMappings_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ProviderName")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("ProviderSecrets")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("ProviderMapping_ProviderMappings_Id");
+
+                    b.ToTable("ProviderMapping");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Rating", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("RatingSource_RatingType_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<double>("Value")
+                        .HasColumnType("REAL");
+
+                    b.Property<int?>("Votes")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.HasIndex("RatingSource_RatingType_Id");
+
+                    b.ToTable("Rating");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<double>("MaximumValue")
+                        .HasColumnType("REAL");
+
+                    b.Property<int?>("MetadataProviderId_Source_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<double>("MinimumValue")
+                        .HasColumnType("REAL");
+
+                    b.Property<string>("Name")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("MetadataProviderId_Source_Id");
+
+                    b.ToTable("RatingSource");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Release", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<int?>("Release_Releases_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Release_Releases_Id");
+
+                    b.ToTable("Release");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("AudioLanguagePreference")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("AuthenticationProviderId")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<bool?>("DisplayCollectionsView")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("DisplayMissingEpisodes")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("EnableNextEpisodeAutoPlay")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("EnableUserPreferenceAccess")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("GroupedFolders")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<bool?>("HidePlayedInLatest")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("InvalidLoginAttemptCount")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("LatestItemExcludes")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<int?>("LoginAttemptsBeforeLockout")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("MustUpdatePassword")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("MyMediaExcludes")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("OrderedViews")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Password")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<bool>("PlayDefaultAudioTrack")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("RememberAudioSelections")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("RememberSubtitleSelections")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("SubtitleLanguagePrefernce")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("SubtitleMode")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("Username")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.HasKey("Id");
+
+                    b.ToTable("User");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Book", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("Book");
+
+                    b.HasDiscriminator().HasValue("Book");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CustomItem", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("LibraryItem");
+
+                    b.HasDiscriminator().HasValue("CustomItem");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.Property<int?>("EpisodeNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Episode_Episodes_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("Episode_Episodes_Id");
+
+                    b.ToTable("Episode");
+
+                    b.HasDiscriminator().HasValue("Episode");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Movie", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("Movie");
+
+                    b.HasDiscriminator().HasValue("Movie");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbum", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("MusicAlbum");
+
+                    b.HasDiscriminator().HasValue("MusicAlbum");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Photo", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("Photo");
+
+                    b.HasDiscriminator().HasValue("Photo");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Season", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.Property<int?>("SeasonNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Season_Seasons_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("Season_Seasons_Id");
+
+                    b.ToTable("Season");
+
+                    b.HasDiscriminator().HasValue("Season");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Series", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.Property<int?>("AirsDayOfWeek")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTimeOffset?>("AirsTime")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTimeOffset?>("FirstAired")
+                        .HasColumnType("TEXT");
+
+                    b.ToTable("Series");
+
+                    b.HasDiscriminator().HasValue("Series");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Track", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.Property<int?>("TrackNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Track_Tracks_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("Track_Tracks_Id");
+
+                    b.ToTable("Track");
+
+                    b.HasDiscriminator().HasValue("Track");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("BookMetadata_BookMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<long?>("ISBN")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("BookMetadata_BookMetadata_Id");
+
+                    b.ToTable("Metadata");
+
+                    b.HasDiscriminator().HasValue("BookMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("CompanyMetadata_CompanyMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Country")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(2);
+
+                    b.Property<string>("Description")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Headquarters")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("Homepage")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasIndex("CompanyMetadata_CompanyMetadata_Id");
+
+                    b.ToTable("CompanyMetadata");
+
+                    b.HasDiscriminator().HasValue("CompanyMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("CustomItemMetadata_CustomItemMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("CustomItemMetadata_CustomItemMetadata_Id");
+
+                    b.ToTable("CustomItemMetadata");
+
+                    b.HasDiscriminator().HasValue("CustomItemMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("EpisodeMetadata_EpisodeMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Outline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<string>("Plot")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Tagline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasIndex("EpisodeMetadata_EpisodeMetadata_Id");
+
+                    b.ToTable("EpisodeMetadata");
+
+                    b.HasDiscriminator().HasValue("EpisodeMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<string>("Country")
+                        .HasColumnName("MovieMetadata_Country")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(2);
+
+                    b.Property<int?>("MovieMetadata_MovieMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Outline")
+                        .HasColumnName("MovieMetadata_Outline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<string>("Plot")
+                        .HasColumnName("MovieMetadata_Plot")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Tagline")
+                        .HasColumnName("MovieMetadata_Tagline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasIndex("MovieMetadata_MovieMetadata_Id");
+
+                    b.ToTable("MovieMetadata");
+
+                    b.HasDiscriminator().HasValue("MovieMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<string>("Barcode")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("Country")
+                        .HasColumnName("MusicAlbumMetadata_Country")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(2);
+
+                    b.Property<string>("LabelNumber")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<int?>("MusicAlbumMetadata_MusicAlbumMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("MusicAlbumMetadata_MusicAlbumMetadata_Id");
+
+                    b.ToTable("MusicAlbumMetadata");
+
+                    b.HasDiscriminator().HasValue("MusicAlbumMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("PhotoMetadata_PhotoMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("PhotoMetadata_PhotoMetadata_Id");
+
+                    b.ToTable("PhotoMetadata");
+
+                    b.HasDiscriminator().HasValue("PhotoMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<string>("Outline")
+                        .HasColumnName("SeasonMetadata_Outline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<int?>("SeasonMetadata_SeasonMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("SeasonMetadata_SeasonMetadata_Id");
+
+                    b.ToTable("SeasonMetadata");
+
+                    b.HasDiscriminator().HasValue("SeasonMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<string>("Country")
+                        .HasColumnName("SeriesMetadata_Country")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(2);
+
+                    b.Property<string>("Outline")
+                        .HasColumnName("SeriesMetadata_Outline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<string>("Plot")
+                        .HasColumnName("SeriesMetadata_Plot")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<int?>("SeriesMetadata_SeriesMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Tagline")
+                        .HasColumnName("SeriesMetadata_Tagline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasIndex("SeriesMetadata_SeriesMetadata_Id");
+
+                    b.ToTable("SeriesMetadata");
+
+                    b.HasDiscriminator().HasValue("SeriesMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("TrackMetadata_TrackMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("TrackMetadata_TrackMetadata_Id");
+
+                    b.ToTable("TrackMetadata");
+
+                    b.HasDiscriminator().HasValue("TrackMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("Artwork")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Release", null)
+                        .WithMany("Chapters")
+                        .HasForeignKey("Chapter_Chapters_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Collection", null)
+                        .WithMany("CollectionItem")
+                        .HasForeignKey("CollectionItem_CollectionItem_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Next")
+                        .WithMany()
+                        .HasForeignKey("CollectionItem_Next_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Previous")
+                        .WithMany()
+                        .HasForeignKey("CollectionItem_Previous_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.LibraryItem", "LibraryItem")
+                        .WithMany()
+                        .HasForeignKey("LibraryItem_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Company", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MusicAlbumMetadata", null)
+                        .WithMany("Labels")
+                        .HasForeignKey("Company_Labels_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.SeriesMetadata", null)
+                        .WithMany("Networks")
+                        .HasForeignKey("Company_Networks_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.Company", "Parent")
+                        .WithMany()
+                        .HasForeignKey("Company_Parent_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.BookMetadata", null)
+                        .WithMany("Publishers")
+                        .HasForeignKey("Company_Publishers_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.MovieMetadata", null)
+                        .WithMany("Studios")
+                        .HasForeignKey("Company_Studios_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("Genres")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("Groups")
+                        .HasForeignKey("Group_Groups_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.LibraryItem", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.LibraryRoot", "LibraryRoot")
+                        .WithMany()
+                        .HasForeignKey("LibraryRoot_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Library", "Library")
+                        .WithMany()
+                        .HasForeignKey("Library_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Release", null)
+                        .WithMany("MediaFiles")
+                        .HasForeignKey("MediaFile_MediaFiles_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MediaFile", null)
+                        .WithMany("MediaFileStreams")
+                        .HasForeignKey("MediaFileStream_MediaFileStreams_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Person", null)
+                        .WithMany("Sources")
+                        .HasForeignKey("MetadataProviderId_Sources_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.PersonRole", null)
+                        .WithMany("Sources")
+                        .HasForeignKey("MetadataProviderId_Sources_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.MetadataProvider", "MetadataProvider")
+                        .WithMany()
+                        .HasForeignKey("MetadataProvider_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("Sources")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Group", null)
+                        .WithMany("GroupPermissions")
+                        .HasForeignKey("Permission_GroupPermissions_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("Permissions")
+                        .HasForeignKey("Permission_Permissions_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.PersonRole", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Artwork", "Artwork")
+                        .WithMany()
+                        .HasForeignKey("Artwork_Artwork_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("PersonRoles")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.Person", "Person")
+                        .WithMany()
+                        .HasForeignKey("Person_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Group", null)
+                        .WithMany("Preferences")
+                        .HasForeignKey("Preference_Preferences_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("Preferences")
+                        .HasForeignKey("Preference_Preferences_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Group", null)
+                        .WithMany("ProviderMappings")
+                        .HasForeignKey("ProviderMapping_ProviderMappings_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("ProviderMappings")
+                        .HasForeignKey("ProviderMapping_ProviderMappings_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Rating", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("Ratings")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.RatingSource", "RatingType")
+                        .WithMany()
+                        .HasForeignKey("RatingSource_RatingType_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MetadataProviderId", "Source")
+                        .WithMany()
+                        .HasForeignKey("MetadataProviderId_Source_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Release", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Book", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.CustomItem", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id1");
+
+                    b.HasOne("Jellyfin.Data.Entities.Episode", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id2");
+
+                    b.HasOne("Jellyfin.Data.Entities.Movie", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id3");
+
+                    b.HasOne("Jellyfin.Data.Entities.Photo", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id4");
+
+                    b.HasOne("Jellyfin.Data.Entities.Track", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id5");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Season", null)
+                        .WithMany("Episodes")
+                        .HasForeignKey("Episode_Episodes_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Season", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Series", null)
+                        .WithMany("Seasons")
+                        .HasForeignKey("Season_Seasons_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Track", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null)
+                        .WithMany("Tracks")
+                        .HasForeignKey("Track_Tracks_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Book", null)
+                        .WithMany("BookMetadata")
+                        .HasForeignKey("BookMetadata_BookMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Company", null)
+                        .WithMany("CompanyMetadata")
+                        .HasForeignKey("CompanyMetadata_CompanyMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.CustomItem", null)
+                        .WithMany("CustomItemMetadata")
+                        .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Episode", null)
+                        .WithMany("EpisodeMetadata")
+                        .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Movie", null)
+                        .WithMany("MovieMetadata")
+                        .HasForeignKey("MovieMetadata_MovieMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null)
+                        .WithMany("MusicAlbumMetadata")
+                        .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Photo", null)
+                        .WithMany("PhotoMetadata")
+                        .HasForeignKey("PhotoMetadata_PhotoMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Season", null)
+                        .WithMany("SeasonMetadata")
+                        .HasForeignKey("SeasonMetadata_SeasonMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Series", null)
+                        .WithMany("SeriesMetadata")
+                        .HasForeignKey("SeriesMetadata_SeriesMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Track", null)
+                        .WithMany("TrackMetadata")
+                        .HasForeignKey("TrackMetadata_TrackMetadata_Id");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}

+ 1294 - 0
Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs

@@ -0,0 +1,1294 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1601
+
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+    public partial class InitialSchema : Migration
+    {
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.EnsureSchema(
+                name: "jellyfin");
+
+            migrationBuilder.CreateTable(
+                name: "ActivityLog",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 512, nullable: false),
+                    Overview = table.Column<string>(maxLength: 512, nullable: true),
+                    ShortOverview = table.Column<string>(maxLength: 512, nullable: true),
+                    Type = table.Column<string>(maxLength: 256, nullable: false),
+                    UserId = table.Column<Guid>(nullable: false),
+                    ItemId = table.Column<string>(maxLength: 256, nullable: true),
+                    DateCreated = table.Column<DateTime>(nullable: false),
+                    LogSeverity = table.Column<int>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_ActivityLog", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Collection",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 1024, nullable: true),
+                    RowVersion = table.Column<uint>(nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Collection", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Library",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 1024, nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Library", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "MetadataProvider",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 1024, nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_MetadataProvider", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Person",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    UrlId = table.Column<Guid>(nullable: false),
+                    Name = table.Column<string>(maxLength: 1024, nullable: false),
+                    SourceId = table.Column<string>(maxLength: 255, nullable: true),
+                    DateAdded = table.Column<DateTime>(nullable: false),
+                    DateModified = table.Column<DateTime>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Person", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "User",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Username = table.Column<string>(maxLength: 255, nullable: false),
+                    Password = table.Column<string>(maxLength: 65535, nullable: true),
+                    MustUpdatePassword = table.Column<bool>(nullable: false),
+                    AudioLanguagePreference = table.Column<string>(maxLength: 255, nullable: false),
+                    AuthenticationProviderId = table.Column<string>(maxLength: 255, nullable: false),
+                    GroupedFolders = table.Column<string>(maxLength: 65535, nullable: true),
+                    InvalidLoginAttemptCount = table.Column<int>(nullable: false),
+                    LatestItemExcludes = table.Column<string>(maxLength: 65535, nullable: true),
+                    LoginAttemptsBeforeLockout = table.Column<int>(nullable: true),
+                    MyMediaExcludes = table.Column<string>(maxLength: 65535, nullable: true),
+                    OrderedViews = table.Column<string>(maxLength: 65535, nullable: true),
+                    SubtitleMode = table.Column<string>(maxLength: 255, nullable: false),
+                    PlayDefaultAudioTrack = table.Column<bool>(nullable: false),
+                    SubtitleLanguagePrefernce = table.Column<string>(maxLength: 255, nullable: true),
+                    DisplayMissingEpisodes = table.Column<bool>(nullable: true),
+                    DisplayCollectionsView = table.Column<bool>(nullable: true),
+                    HidePlayedInLatest = table.Column<bool>(nullable: true),
+                    RememberAudioSelections = table.Column<bool>(nullable: true),
+                    RememberSubtitleSelections = table.Column<bool>(nullable: true),
+                    EnableNextEpisodeAutoPlay = table.Column<bool>(nullable: true),
+                    EnableUserPreferenceAccess = table.Column<bool>(nullable: true),
+                    RowVersion = table.Column<uint>(nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_User", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "LibraryRoot",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Path = table.Column<string>(maxLength: 65535, nullable: false),
+                    NetworkPath = table.Column<string>(maxLength: 65535, nullable: true),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Library_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_LibraryRoot", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_LibraryRoot_Library_Library_Id",
+                        column: x => x.Library_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Library",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Group",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 255, nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Group_Groups_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Group", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Group_User_Group_Groups_Id",
+                        column: x => x.Group_Groups_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "User",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "LibraryItem",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    UrlId = table.Column<Guid>(nullable: false),
+                    DateAdded = table.Column<DateTime>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    LibraryRoot_Id = table.Column<int>(nullable: true),
+                    Discriminator = table.Column<string>(nullable: false),
+                    EpisodeNumber = table.Column<int>(nullable: true),
+                    Episode_Episodes_Id = table.Column<int>(nullable: true),
+                    SeasonNumber = table.Column<int>(nullable: true),
+                    Season_Seasons_Id = table.Column<int>(nullable: true),
+                    AirsDayOfWeek = table.Column<int>(nullable: true),
+                    AirsTime = table.Column<DateTimeOffset>(nullable: true),
+                    FirstAired = table.Column<DateTimeOffset>(nullable: true),
+                    TrackNumber = table.Column<int>(nullable: true),
+                    Track_Tracks_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_LibraryItem", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_LibraryItem_LibraryItem_Episode_Episodes_Id",
+                        column: x => x.Episode_Episodes_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_LibraryItem_LibraryRoot_LibraryRoot_Id",
+                        column: x => x.LibraryRoot_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryRoot",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_LibraryItem_LibraryItem_Season_Seasons_Id",
+                        column: x => x.Season_Seasons_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_LibraryItem_LibraryItem_Track_Tracks_Id",
+                        column: x => x.Track_Tracks_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Permission",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Kind = table.Column<int>(nullable: false),
+                    Value = table.Column<bool>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Permission_GroupPermissions_Id = table.Column<int>(nullable: true),
+                    Permission_Permissions_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Permission", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Permission_Group_Permission_GroupPermissions_Id",
+                        column: x => x.Permission_GroupPermissions_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Group",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Permission_User_Permission_Permissions_Id",
+                        column: x => x.Permission_Permissions_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "User",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Preference",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Kind = table.Column<int>(nullable: false),
+                    Value = table.Column<string>(maxLength: 65535, nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Preference_Preferences_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Preference", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Preference_Group_Preference_Preferences_Id",
+                        column: x => x.Preference_Preferences_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Group",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Preference_User_Preference_Preferences_Id",
+                        column: x => x.Preference_Preferences_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "User",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "ProviderMapping",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    ProviderName = table.Column<string>(maxLength: 255, nullable: false),
+                    ProviderSecrets = table.Column<string>(maxLength: 65535, nullable: false),
+                    ProviderData = table.Column<string>(maxLength: 65535, nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    ProviderMapping_ProviderMappings_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_ProviderMapping", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_ProviderMapping_Group_ProviderMapping_ProviderMappings_Id",
+                        column: x => x.ProviderMapping_ProviderMappings_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Group",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_ProviderMapping_User_ProviderMapping_ProviderMappings_Id",
+                        column: x => x.ProviderMapping_ProviderMappings_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "User",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "CollectionItem",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    LibraryItem_Id = table.Column<int>(nullable: true),
+                    CollectionItem_Next_Id = table.Column<int>(nullable: true),
+                    CollectionItem_Previous_Id = table.Column<int>(nullable: true),
+                    CollectionItem_CollectionItem_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_CollectionItem", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_CollectionItem_Collection_CollectionItem_CollectionItem_Id",
+                        column: x => x.CollectionItem_CollectionItem_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Collection",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_CollectionItem_CollectionItem_CollectionItem_Next_Id",
+                        column: x => x.CollectionItem_Next_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "CollectionItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_CollectionItem_CollectionItem_CollectionItem_Previous_Id",
+                        column: x => x.CollectionItem_Previous_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "CollectionItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_CollectionItem_LibraryItem_LibraryItem_Id",
+                        column: x => x.LibraryItem_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Release",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 1024, nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Release_Releases_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Release", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Release_LibraryItem_Release_Releases_Id",
+                        column: x => x.Release_Releases_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Release_LibraryItem_Release_Releases_Id1",
+                        column: x => x.Release_Releases_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Release_LibraryItem_Release_Releases_Id2",
+                        column: x => x.Release_Releases_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Release_LibraryItem_Release_Releases_Id3",
+                        column: x => x.Release_Releases_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Release_LibraryItem_Release_Releases_Id4",
+                        column: x => x.Release_Releases_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Release_LibraryItem_Release_Releases_Id5",
+                        column: x => x.Release_Releases_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Chapter",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 1024, nullable: true),
+                    Language = table.Column<string>(maxLength: 3, nullable: false),
+                    TimeStart = table.Column<long>(nullable: false),
+                    TimeEnd = table.Column<long>(nullable: true),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Chapter_Chapters_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Chapter", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Chapter_Release_Chapter_Chapters_Id",
+                        column: x => x.Chapter_Chapters_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Release",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "MediaFile",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Path = table.Column<string>(maxLength: 65535, nullable: false),
+                    Kind = table.Column<int>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    MediaFile_MediaFiles_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_MediaFile", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_MediaFile_Release_MediaFile_MediaFiles_Id",
+                        column: x => x.MediaFile_MediaFiles_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Release",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "MediaFileStream",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    StreamNumber = table.Column<int>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    MediaFileStream_MediaFileStreams_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_MediaFileStream", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_MediaFileStream_MediaFile_MediaFileStream_MediaFileStreams_Id",
+                        column: x => x.MediaFileStream_MediaFileStreams_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "MediaFile",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "PersonRole",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Role = table.Column<string>(maxLength: 1024, nullable: true),
+                    Type = table.Column<int>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Person_Id = table.Column<int>(nullable: true),
+                    Artwork_Artwork_Id = table.Column<int>(nullable: true),
+                    PersonRole_PersonRoles_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_PersonRole", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_PersonRole_Person_Person_Id",
+                        column: x => x.Person_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Person",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Metadata",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Title = table.Column<string>(maxLength: 1024, nullable: false),
+                    OriginalTitle = table.Column<string>(maxLength: 1024, nullable: true),
+                    SortTitle = table.Column<string>(maxLength: 1024, nullable: true),
+                    Language = table.Column<string>(maxLength: 3, nullable: false),
+                    ReleaseDate = table.Column<DateTimeOffset>(nullable: true),
+                    DateAdded = table.Column<DateTime>(nullable: false),
+                    DateModified = table.Column<DateTime>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Discriminator = table.Column<string>(nullable: false),
+                    ISBN = table.Column<long>(nullable: true),
+                    BookMetadata_BookMetadata_Id = table.Column<int>(nullable: true),
+                    Description = table.Column<string>(maxLength: 65535, nullable: true),
+                    Headquarters = table.Column<string>(maxLength: 255, nullable: true),
+                    Country = table.Column<string>(maxLength: 2, nullable: true),
+                    Homepage = table.Column<string>(maxLength: 1024, nullable: true),
+                    CompanyMetadata_CompanyMetadata_Id = table.Column<int>(nullable: true),
+                    CustomItemMetadata_CustomItemMetadata_Id = table.Column<int>(nullable: true),
+                    Outline = table.Column<string>(maxLength: 1024, nullable: true),
+                    Plot = table.Column<string>(maxLength: 65535, nullable: true),
+                    Tagline = table.Column<string>(maxLength: 1024, nullable: true),
+                    EpisodeMetadata_EpisodeMetadata_Id = table.Column<int>(nullable: true),
+                    MovieMetadata_Outline = table.Column<string>(maxLength: 1024, nullable: true),
+                    MovieMetadata_Plot = table.Column<string>(maxLength: 65535, nullable: true),
+                    MovieMetadata_Tagline = table.Column<string>(maxLength: 1024, nullable: true),
+                    MovieMetadata_Country = table.Column<string>(maxLength: 2, nullable: true),
+                    MovieMetadata_MovieMetadata_Id = table.Column<int>(nullable: true),
+                    Barcode = table.Column<string>(maxLength: 255, nullable: true),
+                    LabelNumber = table.Column<string>(maxLength: 255, nullable: true),
+                    MusicAlbumMetadata_Country = table.Column<string>(maxLength: 2, nullable: true),
+                    MusicAlbumMetadata_MusicAlbumMetadata_Id = table.Column<int>(nullable: true),
+                    PhotoMetadata_PhotoMetadata_Id = table.Column<int>(nullable: true),
+                    SeasonMetadata_Outline = table.Column<string>(maxLength: 1024, nullable: true),
+                    SeasonMetadata_SeasonMetadata_Id = table.Column<int>(nullable: true),
+                    SeriesMetadata_Outline = table.Column<string>(maxLength: 1024, nullable: true),
+                    SeriesMetadata_Plot = table.Column<string>(maxLength: 65535, nullable: true),
+                    SeriesMetadata_Tagline = table.Column<string>(maxLength: 1024, nullable: true),
+                    SeriesMetadata_Country = table.Column<string>(maxLength: 2, nullable: true),
+                    SeriesMetadata_SeriesMetadata_Id = table.Column<int>(nullable: true),
+                    TrackMetadata_TrackMetadata_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Metadata", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_BookMetadata_BookMetadata_Id",
+                        column: x => x.BookMetadata_BookMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_CustomItemMetadata_CustomItemMetadata_Id",
+                        column: x => x.CustomItemMetadata_CustomItemMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_EpisodeMetadata_EpisodeMetadata_Id",
+                        column: x => x.EpisodeMetadata_EpisodeMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_MovieMetadata_MovieMetadata_Id",
+                        column: x => x.MovieMetadata_MovieMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_MusicAlbumMetadata_MusicAlbumMetadata_Id",
+                        column: x => x.MusicAlbumMetadata_MusicAlbumMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_PhotoMetadata_PhotoMetadata_Id",
+                        column: x => x.PhotoMetadata_PhotoMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_SeasonMetadata_SeasonMetadata_Id",
+                        column: x => x.SeasonMetadata_SeasonMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_SeriesMetadata_SeriesMetadata_Id",
+                        column: x => x.SeriesMetadata_SeriesMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Metadata_LibraryItem_TrackMetadata_TrackMetadata_Id",
+                        column: x => x.TrackMetadata_TrackMetadata_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "LibraryItem",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Artwork",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Path = table.Column<string>(maxLength: 65535, nullable: false),
+                    Kind = table.Column<int>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    PersonRole_PersonRoles_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Artwork", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Artwork_Metadata_PersonRole_PersonRoles_Id",
+                        column: x => x.PersonRole_PersonRoles_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Metadata",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Company",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    Company_Parent_Id = table.Column<int>(nullable: true),
+                    Company_Labels_Id = table.Column<int>(nullable: true),
+                    Company_Networks_Id = table.Column<int>(nullable: true),
+                    Company_Publishers_Id = table.Column<int>(nullable: true),
+                    Company_Studios_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Company", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Company_Metadata_Company_Labels_Id",
+                        column: x => x.Company_Labels_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Metadata",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Company_Metadata_Company_Networks_Id",
+                        column: x => x.Company_Networks_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Metadata",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Company_Company_Company_Parent_Id",
+                        column: x => x.Company_Parent_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Company",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Company_Metadata_Company_Publishers_Id",
+                        column: x => x.Company_Publishers_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Metadata",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Company_Metadata_Company_Studios_Id",
+                        column: x => x.Company_Studios_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Metadata",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Genre",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 255, nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    PersonRole_PersonRoles_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Genre", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Genre_Metadata_PersonRole_PersonRoles_Id",
+                        column: x => x.PersonRole_PersonRoles_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Metadata",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "MetadataProviderId",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    ProviderId = table.Column<string>(maxLength: 255, nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    MetadataProvider_Id = table.Column<int>(nullable: true),
+                    MetadataProviderId_Sources_Id = table.Column<int>(nullable: true),
+                    PersonRole_PersonRoles_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_MetadataProviderId", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_MetadataProviderId_Person_MetadataProviderId_Sources_Id",
+                        column: x => x.MetadataProviderId_Sources_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Person",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_MetadataProviderId_PersonRole_MetadataProviderId_Sources_Id",
+                        column: x => x.MetadataProviderId_Sources_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "PersonRole",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_MetadataProviderId_MetadataProvider_MetadataProvider_Id",
+                        column: x => x.MetadataProvider_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "MetadataProvider",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_MetadataProviderId_Metadata_PersonRole_PersonRoles_Id",
+                        column: x => x.PersonRole_PersonRoles_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Metadata",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "RatingSource",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Name = table.Column<string>(maxLength: 1024, nullable: true),
+                    MaximumValue = table.Column<double>(nullable: false),
+                    MinimumValue = table.Column<double>(nullable: false),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    MetadataProviderId_Source_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_RatingSource", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_RatingSource_MetadataProviderId_MetadataProviderId_Source_Id",
+                        column: x => x.MetadataProviderId_Source_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "MetadataProviderId",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "Rating",
+                schema: "jellyfin",
+                columns: table => new
+                {
+                    Id = table.Column<int>(nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    Value = table.Column<double>(nullable: false),
+                    Votes = table.Column<int>(nullable: true),
+                    RowVersion = table.Column<uint>(nullable: false),
+                    RatingSource_RatingType_Id = table.Column<int>(nullable: true),
+                    PersonRole_PersonRoles_Id = table.Column<int>(nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_Rating", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_Rating_Metadata_PersonRole_PersonRoles_Id",
+                        column: x => x.PersonRole_PersonRoles_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "Metadata",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                    table.ForeignKey(
+                        name: "FK_Rating_RatingSource_RatingSource_RatingType_Id",
+                        column: x => x.RatingSource_RatingType_Id,
+                        principalSchema: "jellyfin",
+                        principalTable: "RatingSource",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Restrict);
+                });
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Artwork_Kind",
+                schema: "jellyfin",
+                table: "Artwork",
+                column: "Kind");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Artwork_PersonRole_PersonRoles_Id",
+                schema: "jellyfin",
+                table: "Artwork",
+                column: "PersonRole_PersonRoles_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Chapter_Chapter_Chapters_Id",
+                schema: "jellyfin",
+                table: "Chapter",
+                column: "Chapter_Chapters_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_CollectionItem_CollectionItem_CollectionItem_Id",
+                schema: "jellyfin",
+                table: "CollectionItem",
+                column: "CollectionItem_CollectionItem_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_CollectionItem_CollectionItem_Next_Id",
+                schema: "jellyfin",
+                table: "CollectionItem",
+                column: "CollectionItem_Next_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_CollectionItem_CollectionItem_Previous_Id",
+                schema: "jellyfin",
+                table: "CollectionItem",
+                column: "CollectionItem_Previous_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_CollectionItem_LibraryItem_Id",
+                schema: "jellyfin",
+                table: "CollectionItem",
+                column: "LibraryItem_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Company_Company_Labels_Id",
+                schema: "jellyfin",
+                table: "Company",
+                column: "Company_Labels_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Company_Company_Networks_Id",
+                schema: "jellyfin",
+                table: "Company",
+                column: "Company_Networks_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Company_Company_Parent_Id",
+                schema: "jellyfin",
+                table: "Company",
+                column: "Company_Parent_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Company_Company_Publishers_Id",
+                schema: "jellyfin",
+                table: "Company",
+                column: "Company_Publishers_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Company_Company_Studios_Id",
+                schema: "jellyfin",
+                table: "Company",
+                column: "Company_Studios_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Genre_Name",
+                schema: "jellyfin",
+                table: "Genre",
+                column: "Name",
+                unique: true);
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Genre_PersonRole_PersonRoles_Id",
+                schema: "jellyfin",
+                table: "Genre",
+                column: "PersonRole_PersonRoles_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Group_Group_Groups_Id",
+                schema: "jellyfin",
+                table: "Group",
+                column: "Group_Groups_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_LibraryItem_Episode_Episodes_Id",
+                schema: "jellyfin",
+                table: "LibraryItem",
+                column: "Episode_Episodes_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_LibraryItem_LibraryRoot_Id",
+                schema: "jellyfin",
+                table: "LibraryItem",
+                column: "LibraryRoot_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_LibraryItem_UrlId",
+                schema: "jellyfin",
+                table: "LibraryItem",
+                column: "UrlId",
+                unique: true);
+
+            migrationBuilder.CreateIndex(
+                name: "IX_LibraryItem_Season_Seasons_Id",
+                schema: "jellyfin",
+                table: "LibraryItem",
+                column: "Season_Seasons_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_LibraryItem_Track_Tracks_Id",
+                schema: "jellyfin",
+                table: "LibraryItem",
+                column: "Track_Tracks_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_LibraryRoot_Library_Id",
+                schema: "jellyfin",
+                table: "LibraryRoot",
+                column: "Library_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_MediaFile_MediaFile_MediaFiles_Id",
+                schema: "jellyfin",
+                table: "MediaFile",
+                column: "MediaFile_MediaFiles_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_MediaFileStream_MediaFileStream_MediaFileStreams_Id",
+                schema: "jellyfin",
+                table: "MediaFileStream",
+                column: "MediaFileStream_MediaFileStreams_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_BookMetadata_BookMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "BookMetadata_BookMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_CompanyMetadata_CompanyMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "CompanyMetadata_CompanyMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_CustomItemMetadata_CustomItemMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "CustomItemMetadata_CustomItemMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_EpisodeMetadata_EpisodeMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "EpisodeMetadata_EpisodeMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_MovieMetadata_MovieMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "MovieMetadata_MovieMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_MusicAlbumMetadata_MusicAlbumMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "MusicAlbumMetadata_MusicAlbumMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_PhotoMetadata_PhotoMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "PhotoMetadata_PhotoMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_SeasonMetadata_SeasonMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "SeasonMetadata_SeasonMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_SeriesMetadata_SeriesMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "SeriesMetadata_SeriesMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Metadata_TrackMetadata_TrackMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "TrackMetadata_TrackMetadata_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_MetadataProviderId_MetadataProviderId_Sources_Id",
+                schema: "jellyfin",
+                table: "MetadataProviderId",
+                column: "MetadataProviderId_Sources_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_MetadataProviderId_MetadataProvider_Id",
+                schema: "jellyfin",
+                table: "MetadataProviderId",
+                column: "MetadataProvider_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_MetadataProviderId_PersonRole_PersonRoles_Id",
+                schema: "jellyfin",
+                table: "MetadataProviderId",
+                column: "PersonRole_PersonRoles_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Permission_Permission_GroupPermissions_Id",
+                schema: "jellyfin",
+                table: "Permission",
+                column: "Permission_GroupPermissions_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Permission_Permission_Permissions_Id",
+                schema: "jellyfin",
+                table: "Permission",
+                column: "Permission_Permissions_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_PersonRole_Artwork_Artwork_Id",
+                schema: "jellyfin",
+                table: "PersonRole",
+                column: "Artwork_Artwork_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_PersonRole_PersonRole_PersonRoles_Id",
+                schema: "jellyfin",
+                table: "PersonRole",
+                column: "PersonRole_PersonRoles_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_PersonRole_Person_Id",
+                schema: "jellyfin",
+                table: "PersonRole",
+                column: "Person_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Preference_Preference_Preferences_Id",
+                schema: "jellyfin",
+                table: "Preference",
+                column: "Preference_Preferences_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_ProviderMapping_ProviderMapping_ProviderMappings_Id",
+                schema: "jellyfin",
+                table: "ProviderMapping",
+                column: "ProviderMapping_ProviderMappings_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Rating_PersonRole_PersonRoles_Id",
+                schema: "jellyfin",
+                table: "Rating",
+                column: "PersonRole_PersonRoles_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Rating_RatingSource_RatingType_Id",
+                schema: "jellyfin",
+                table: "Rating",
+                column: "RatingSource_RatingType_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_RatingSource_MetadataProviderId_Source_Id",
+                schema: "jellyfin",
+                table: "RatingSource",
+                column: "MetadataProviderId_Source_Id");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Release_Release_Releases_Id",
+                schema: "jellyfin",
+                table: "Release",
+                column: "Release_Releases_Id");
+
+            migrationBuilder.AddForeignKey(
+                name: "FK_PersonRole_Metadata_PersonRole_PersonRoles_Id",
+                schema: "jellyfin",
+                table: "PersonRole",
+                column: "PersonRole_PersonRoles_Id",
+                principalSchema: "jellyfin",
+                principalTable: "Metadata",
+                principalColumn: "Id",
+                onDelete: ReferentialAction.Restrict);
+
+            migrationBuilder.AddForeignKey(
+                name: "FK_PersonRole_Artwork_Artwork_Artwork_Id",
+                schema: "jellyfin",
+                table: "PersonRole",
+                column: "Artwork_Artwork_Id",
+                principalSchema: "jellyfin",
+                principalTable: "Artwork",
+                principalColumn: "Id",
+                onDelete: ReferentialAction.Restrict);
+
+            migrationBuilder.AddForeignKey(
+                name: "FK_Metadata_Company_CompanyMetadata_CompanyMetadata_Id",
+                schema: "jellyfin",
+                table: "Metadata",
+                column: "CompanyMetadata_CompanyMetadata_Id",
+                principalSchema: "jellyfin",
+                principalTable: "Company",
+                principalColumn: "Id",
+                onDelete: ReferentialAction.Restrict);
+        }
+
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropForeignKey(
+                name: "FK_Company_Metadata_Company_Labels_Id",
+                schema: "jellyfin",
+                table: "Company");
+
+            migrationBuilder.DropForeignKey(
+                name: "FK_Company_Metadata_Company_Networks_Id",
+                schema: "jellyfin",
+                table: "Company");
+
+            migrationBuilder.DropForeignKey(
+                name: "FK_Company_Metadata_Company_Publishers_Id",
+                schema: "jellyfin",
+                table: "Company");
+
+            migrationBuilder.DropForeignKey(
+                name: "FK_Company_Metadata_Company_Studios_Id",
+                schema: "jellyfin",
+                table: "Company");
+
+            migrationBuilder.DropTable(
+                name: "ActivityLog",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Chapter",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "CollectionItem",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Genre",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "MediaFileStream",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Permission",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Preference",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "ProviderMapping",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Rating",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Collection",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "MediaFile",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Group",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "RatingSource",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Release",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "User",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "MetadataProviderId",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "PersonRole",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "MetadataProvider",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Artwork",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Person",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Metadata",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "LibraryItem",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Company",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "LibraryRoot",
+                schema: "jellyfin");
+
+            migrationBuilder.DropTable(
+                name: "Library",
+                schema: "jellyfin");
+        }
+    }
+}

+ 20 - 0
Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs

@@ -0,0 +1,20 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+    /// <summary>
+    /// The design time factory for <see cref="JellyfinDb"/>.
+    /// This is only used for the creation of migrations and not during runtime.
+    /// </summary>
+    internal class DesignTimeJellyfinDbFactory : IDesignTimeDbContextFactory<JellyfinDb>
+    {
+        public JellyfinDb CreateDbContext(string[] args)
+        {
+            var optionsBuilder = new DbContextOptionsBuilder<JellyfinDb>();
+            optionsBuilder.UseSqlite("Data Source=jellyfin.db");
+
+            return new JellyfinDb(optionsBuilder.Options);
+        }
+    }
+}

+ 1511 - 0
Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs

@@ -0,0 +1,1511 @@
+#pragma warning disable CS1591
+
+// <auto-generated />
+using System;
+using Jellyfin.Server.Implementations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+    [DbContext(typeof(JellyfinDb))]
+    partial class JellyfinDbModelSnapshot : ModelSnapshot
+    {
+        protected override void BuildModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasDefaultSchema("jellyfin")
+                .HasAnnotation("ProductVersion", "3.1.3");
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateCreated")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("ItemId")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(256);
+
+                    b.Property<int>("LogSeverity")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(512);
+
+                    b.Property<string>("Overview")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(512);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ShortOverview")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(512);
+
+                    b.Property<string>("Type")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(256);
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("ActivityLog");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Kind");
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.ToTable("Artwork");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Chapter_Chapters_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Language")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(3);
+
+                    b.Property<string>("Name")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<long?>("TimeEnd")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<long>("TimeStart")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Chapter_Chapters_Id");
+
+                    b.ToTable("Chapter");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Collection", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Collection");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("CollectionItem_CollectionItem_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("CollectionItem_Next_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("CollectionItem_Previous_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("LibraryItem_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("CollectionItem_CollectionItem_Id");
+
+                    b.HasIndex("CollectionItem_Next_Id");
+
+                    b.HasIndex("CollectionItem_Previous_Id");
+
+                    b.HasIndex("LibraryItem_Id");
+
+                    b.ToTable("CollectionItem");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Company", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Labels_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Networks_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Parent_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Publishers_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Company_Studios_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Company_Labels_Id");
+
+                    b.HasIndex("Company_Networks_Id");
+
+                    b.HasIndex("Company_Parent_Id");
+
+                    b.HasIndex("Company_Publishers_Id");
+
+                    b.HasIndex("Company_Studios_Id");
+
+                    b.ToTable("Company");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Name")
+                        .IsUnique();
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.ToTable("Genre");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Group_Groups_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Group_Groups_Id");
+
+                    b.ToTable("Group");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Library", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Library");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.LibraryItem", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateAdded")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Discriminator")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property<int?>("LibraryRoot_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<Guid>("UrlId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("LibraryRoot_Id");
+
+                    b.HasIndex("UrlId")
+                        .IsUnique();
+
+                    b.ToTable("LibraryItem");
+
+                    b.HasDiscriminator<string>("Discriminator").HasValue("LibraryItem");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Library_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("NetworkPath")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Library_Id");
+
+                    b.ToTable("LibraryRoot");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MediaFile_MediaFiles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("MediaFile_MediaFiles_Id");
+
+                    b.ToTable("MediaFile");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MediaFileStream_MediaFileStreams_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("StreamNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("MediaFileStream_MediaFileStreams_Id");
+
+                    b.ToTable("MediaFileStream");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Metadata", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateAdded")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateModified")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Discriminator")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Language")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(3);
+
+                    b.Property<string>("OriginalTitle")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<DateTimeOffset?>("ReleaseDate")
+                        .HasColumnType("TEXT");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("SortTitle")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<string>("Title")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Metadata");
+
+                    b.HasDiscriminator<string>("Discriminator").HasValue("Metadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProvider", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("MetadataProvider");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MetadataProviderId_Sources_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("MetadataProvider_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ProviderId")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("MetadataProviderId_Sources_Id");
+
+                    b.HasIndex("MetadataProvider_Id");
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.ToTable("MetadataProviderId");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Permission_GroupPermissions_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Permission_Permissions_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("Value")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Permission_GroupPermissions_Id");
+
+                    b.HasIndex("Permission_Permissions_Id");
+
+                    b.ToTable("Permission");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Person", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateAdded")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTime>("DateModified")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("SourceId")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<Guid>("UrlId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Person");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.PersonRole", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Artwork_Artwork_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Person_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Role")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Type")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Artwork_Artwork_Id");
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.HasIndex("Person_Id");
+
+                    b.ToTable("PersonRole");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Kind")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Preference_Preferences_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Value")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Preference_Preferences_Id");
+
+                    b.ToTable("Preference");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ProviderData")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<int?>("ProviderMapping_ProviderMappings_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("ProviderName")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("ProviderSecrets")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("ProviderMapping_ProviderMappings_Id");
+
+                    b.ToTable("ProviderMapping");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Rating", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("PersonRole_PersonRoles_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("RatingSource_RatingType_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<double>("Value")
+                        .HasColumnType("REAL");
+
+                    b.Property<int?>("Votes")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("PersonRole_PersonRoles_Id");
+
+                    b.HasIndex("RatingSource_RatingType_Id");
+
+                    b.ToTable("Rating");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<double>("MaximumValue")
+                        .HasColumnType("REAL");
+
+                    b.Property<int?>("MetadataProviderId_Source_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<double>("MinimumValue")
+                        .HasColumnType("REAL");
+
+                    b.Property<string>("Name")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("MetadataProviderId_Source_Id");
+
+                    b.ToTable("RatingSource");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Release", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<int?>("Release_Releases_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Release_Releases_Id");
+
+                    b.ToTable("Release");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("AudioLanguagePreference")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("AuthenticationProviderId")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<bool?>("DisplayCollectionsView")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("DisplayMissingEpisodes")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("EnableNextEpisodeAutoPlay")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("EnableUserPreferenceAccess")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("GroupedFolders")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<bool?>("HidePlayedInLatest")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("InvalidLoginAttemptCount")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("LatestItemExcludes")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<int?>("LoginAttemptsBeforeLockout")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("MustUpdatePassword")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("MyMediaExcludes")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("OrderedViews")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Password")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<bool>("PlayDefaultAudioTrack")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("RememberAudioSelections")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool?>("RememberSubtitleSelections")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<uint>("RowVersion")
+                        .IsConcurrencyToken()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("SubtitleLanguagePrefernce")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("SubtitleMode")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("Username")
+                        .IsRequired()
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.HasKey("Id");
+
+                    b.ToTable("User");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Book", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("Book");
+
+                    b.HasDiscriminator().HasValue("Book");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CustomItem", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("LibraryItem");
+
+                    b.HasDiscriminator().HasValue("CustomItem");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.Property<int?>("EpisodeNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Episode_Episodes_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("Episode_Episodes_Id");
+
+                    b.ToTable("Episode");
+
+                    b.HasDiscriminator().HasValue("Episode");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Movie", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("Movie");
+
+                    b.HasDiscriminator().HasValue("Movie");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbum", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("MusicAlbum");
+
+                    b.HasDiscriminator().HasValue("MusicAlbum");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Photo", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.ToTable("Photo");
+
+                    b.HasDiscriminator().HasValue("Photo");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Season", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.Property<int?>("SeasonNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Season_Seasons_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("Season_Seasons_Id");
+
+                    b.ToTable("Season");
+
+                    b.HasDiscriminator().HasValue("Season");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Series", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.Property<int?>("AirsDayOfWeek")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTimeOffset?>("AirsTime")
+                        .HasColumnType("TEXT");
+
+                    b.Property<DateTimeOffset?>("FirstAired")
+                        .HasColumnType("TEXT");
+
+                    b.ToTable("Series");
+
+                    b.HasDiscriminator().HasValue("Series");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Track", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.LibraryItem");
+
+                    b.Property<int?>("TrackNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("Track_Tracks_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("Track_Tracks_Id");
+
+                    b.ToTable("Track");
+
+                    b.HasDiscriminator().HasValue("Track");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("BookMetadata_BookMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<long?>("ISBN")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("BookMetadata_BookMetadata_Id");
+
+                    b.ToTable("Metadata");
+
+                    b.HasDiscriminator().HasValue("BookMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("CompanyMetadata_CompanyMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Country")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(2);
+
+                    b.Property<string>("Description")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Headquarters")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("Homepage")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasIndex("CompanyMetadata_CompanyMetadata_Id");
+
+                    b.ToTable("CompanyMetadata");
+
+                    b.HasDiscriminator().HasValue("CompanyMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("CustomItemMetadata_CustomItemMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("CustomItemMetadata_CustomItemMetadata_Id");
+
+                    b.ToTable("CustomItemMetadata");
+
+                    b.HasDiscriminator().HasValue("CustomItemMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("EpisodeMetadata_EpisodeMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Outline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<string>("Plot")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Tagline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasIndex("EpisodeMetadata_EpisodeMetadata_Id");
+
+                    b.ToTable("EpisodeMetadata");
+
+                    b.HasDiscriminator().HasValue("EpisodeMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<string>("Country")
+                        .HasColumnName("MovieMetadata_Country")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(2);
+
+                    b.Property<int?>("MovieMetadata_MovieMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Outline")
+                        .HasColumnName("MovieMetadata_Outline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<string>("Plot")
+                        .HasColumnName("MovieMetadata_Plot")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<string>("Tagline")
+                        .HasColumnName("MovieMetadata_Tagline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasIndex("MovieMetadata_MovieMetadata_Id");
+
+                    b.ToTable("MovieMetadata");
+
+                    b.HasDiscriminator().HasValue("MovieMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<string>("Barcode")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<string>("Country")
+                        .HasColumnName("MusicAlbumMetadata_Country")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(2);
+
+                    b.Property<string>("LabelNumber")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(255);
+
+                    b.Property<int?>("MusicAlbumMetadata_MusicAlbumMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("MusicAlbumMetadata_MusicAlbumMetadata_Id");
+
+                    b.ToTable("MusicAlbumMetadata");
+
+                    b.HasDiscriminator().HasValue("MusicAlbumMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("PhotoMetadata_PhotoMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("PhotoMetadata_PhotoMetadata_Id");
+
+                    b.ToTable("PhotoMetadata");
+
+                    b.HasDiscriminator().HasValue("PhotoMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<string>("Outline")
+                        .HasColumnName("SeasonMetadata_Outline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<int?>("SeasonMetadata_SeasonMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("SeasonMetadata_SeasonMetadata_Id");
+
+                    b.ToTable("SeasonMetadata");
+
+                    b.HasDiscriminator().HasValue("SeasonMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<string>("Country")
+                        .HasColumnName("SeriesMetadata_Country")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(2);
+
+                    b.Property<string>("Outline")
+                        .HasColumnName("SeriesMetadata_Outline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.Property<string>("Plot")
+                        .HasColumnName("SeriesMetadata_Plot")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(65535);
+
+                    b.Property<int?>("SeriesMetadata_SeriesMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Tagline")
+                        .HasColumnName("SeriesMetadata_Tagline")
+                        .HasColumnType("TEXT")
+                        .HasMaxLength(1024);
+
+                    b.HasIndex("SeriesMetadata_SeriesMetadata_Id");
+
+                    b.ToTable("SeriesMetadata");
+
+                    b.HasDiscriminator().HasValue("SeriesMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b =>
+                {
+                    b.HasBaseType("Jellyfin.Data.Entities.Metadata");
+
+                    b.Property<int?>("TrackMetadata_TrackMetadata_Id")
+                        .HasColumnType("INTEGER");
+
+                    b.HasIndex("TrackMetadata_TrackMetadata_Id");
+
+                    b.ToTable("TrackMetadata");
+
+                    b.HasDiscriminator().HasValue("TrackMetadata");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("Artwork")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Release", null)
+                        .WithMany("Chapters")
+                        .HasForeignKey("Chapter_Chapters_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Collection", null)
+                        .WithMany("CollectionItem")
+                        .HasForeignKey("CollectionItem_CollectionItem_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Next")
+                        .WithMany()
+                        .HasForeignKey("CollectionItem_Next_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Previous")
+                        .WithMany()
+                        .HasForeignKey("CollectionItem_Previous_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.LibraryItem", "LibraryItem")
+                        .WithMany()
+                        .HasForeignKey("LibraryItem_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Company", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MusicAlbumMetadata", null)
+                        .WithMany("Labels")
+                        .HasForeignKey("Company_Labels_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.SeriesMetadata", null)
+                        .WithMany("Networks")
+                        .HasForeignKey("Company_Networks_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.Company", "Parent")
+                        .WithMany()
+                        .HasForeignKey("Company_Parent_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.BookMetadata", null)
+                        .WithMany("Publishers")
+                        .HasForeignKey("Company_Publishers_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.MovieMetadata", null)
+                        .WithMany("Studios")
+                        .HasForeignKey("Company_Studios_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("Genres")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("Groups")
+                        .HasForeignKey("Group_Groups_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.LibraryItem", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.LibraryRoot", "LibraryRoot")
+                        .WithMany()
+                        .HasForeignKey("LibraryRoot_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Library", "Library")
+                        .WithMany()
+                        .HasForeignKey("Library_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Release", null)
+                        .WithMany("MediaFiles")
+                        .HasForeignKey("MediaFile_MediaFiles_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MediaFile", null)
+                        .WithMany("MediaFileStreams")
+                        .HasForeignKey("MediaFileStream_MediaFileStreams_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Person", null)
+                        .WithMany("Sources")
+                        .HasForeignKey("MetadataProviderId_Sources_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.PersonRole", null)
+                        .WithMany("Sources")
+                        .HasForeignKey("MetadataProviderId_Sources_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.MetadataProvider", "MetadataProvider")
+                        .WithMany()
+                        .HasForeignKey("MetadataProvider_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("Sources")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Group", null)
+                        .WithMany("GroupPermissions")
+                        .HasForeignKey("Permission_GroupPermissions_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("Permissions")
+                        .HasForeignKey("Permission_Permissions_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.PersonRole", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Artwork", "Artwork")
+                        .WithMany()
+                        .HasForeignKey("Artwork_Artwork_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("PersonRoles")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.Person", "Person")
+                        .WithMany()
+                        .HasForeignKey("Person_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Group", null)
+                        .WithMany("Preferences")
+                        .HasForeignKey("Preference_Preferences_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("Preferences")
+                        .HasForeignKey("Preference_Preferences_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Group", null)
+                        .WithMany("ProviderMappings")
+                        .HasForeignKey("ProviderMapping_ProviderMappings_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.User", null)
+                        .WithMany("ProviderMappings")
+                        .HasForeignKey("ProviderMapping_ProviderMappings_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Rating", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Metadata", null)
+                        .WithMany("Ratings")
+                        .HasForeignKey("PersonRole_PersonRoles_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.RatingSource", "RatingType")
+                        .WithMany()
+                        .HasForeignKey("RatingSource_RatingType_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MetadataProviderId", "Source")
+                        .WithMany()
+                        .HasForeignKey("MetadataProviderId_Source_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Release", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Book", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id");
+
+                    b.HasOne("Jellyfin.Data.Entities.CustomItem", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id1");
+
+                    b.HasOne("Jellyfin.Data.Entities.Episode", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id2");
+
+                    b.HasOne("Jellyfin.Data.Entities.Movie", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id3");
+
+                    b.HasOne("Jellyfin.Data.Entities.Photo", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id4");
+
+                    b.HasOne("Jellyfin.Data.Entities.Track", null)
+                        .WithMany("Releases")
+                        .HasForeignKey("Release_Releases_Id")
+                        .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id5");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Season", null)
+                        .WithMany("Episodes")
+                        .HasForeignKey("Episode_Episodes_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Season", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Series", null)
+                        .WithMany("Seasons")
+                        .HasForeignKey("Season_Seasons_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.Track", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null)
+                        .WithMany("Tracks")
+                        .HasForeignKey("Track_Tracks_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Book", null)
+                        .WithMany("BookMetadata")
+                        .HasForeignKey("BookMetadata_BookMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Company", null)
+                        .WithMany("CompanyMetadata")
+                        .HasForeignKey("CompanyMetadata_CompanyMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.CustomItem", null)
+                        .WithMany("CustomItemMetadata")
+                        .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Episode", null)
+                        .WithMany("EpisodeMetadata")
+                        .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Movie", null)
+                        .WithMany("MovieMetadata")
+                        .HasForeignKey("MovieMetadata_MovieMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null)
+                        .WithMany("MusicAlbumMetadata")
+                        .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Photo", null)
+                        .WithMany("PhotoMetadata")
+                        .HasForeignKey("PhotoMetadata_PhotoMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Season", null)
+                        .WithMany("SeasonMetadata")
+                        .HasForeignKey("SeasonMetadata_SeasonMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Series", null)
+                        .WithMany("SeriesMetadata")
+                        .HasForeignKey("SeriesMetadata_SeriesMetadata_Id");
+                });
+
+            modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b =>
+                {
+                    b.HasOne("Jellyfin.Data.Entities.Track", null)
+                        .WithMany("TrackMetadata")
+                        .HasForeignKey("TrackMetadata_TrackMetadata_Id");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}

+ 7 - 0
Jellyfin.Server/Jellyfin.Server.csproj

@@ -13,6 +13,9 @@
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>
     <Nullable>enable</Nullable>
+
+    <!-- Used for generating migrations for EF Core -->
+    <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
@@ -41,6 +44,10 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="CommandLineParser" Version="2.7.82" />
     <PackageReference Include="CommandLineParser" Version="2.7.82" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.3" />
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.3" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
     <PackageReference Include="prometheus-net" Version="3.5.0" />
     <PackageReference Include="prometheus-net" Version="3.5.0" />

+ 2 - 1
Jellyfin.Server/Migrations/MigrationRunner.cs

@@ -16,7 +16,8 @@ namespace Jellyfin.Server.Migrations
         internal static readonly IMigrationRoutine[] Migrations =
         internal static readonly IMigrationRoutine[] Migrations =
         {
         {
             new Routines.DisableTranscodingThrottling(),
             new Routines.DisableTranscodingThrottling(),
-            new Routines.CreateUserLoggingConfigFile()
+            new Routines.CreateUserLoggingConfigFile(),
+            new Routines.MigrateActivityLogDb()
         };
         };
 
 
         /// <summary>
         /// <summary>

+ 109 - 0
Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs

@@ -0,0 +1,109 @@
+#pragma warning disable CS1591
+
+using System;
+using System.IO;
+using Emby.Server.Implementations.Data;
+using Jellyfin.Data.Entities;
+using Jellyfin.Server.Implementations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using SQLitePCL.pretty;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+    public class MigrateActivityLogDb : IMigrationRoutine
+    {
+        private const string DbFilename = "activitylog.db";
+
+        public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978");
+
+        public string Name => "MigrateActivityLogDatabase";
+
+        public void Perform(CoreAppHost host, ILogger logger)
+        {
+            var dataPath = host.ServerConfigurationManager.ApplicationPaths.DataPath;
+            using (var connection = SQLite3.Open(
+                Path.Combine(dataPath, DbFilename),
+                ConnectionFlags.ReadOnly,
+                null))
+            {
+                logger.LogInformation("Migrating the database may take a while, do not stop Jellyfin.");
+                using var dbContext = host.ServiceProvider.GetService<JellyfinDb>();
+
+                var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC");
+
+                // Make sure that the database is empty in case of failed migration due to power outages, etc.
+                dbContext.ActivityLogs.RemoveRange(dbContext.ActivityLogs);
+                dbContext.SaveChanges();
+                // Reset the autoincrement counter
+                dbContext.Database.ExecuteSqlRaw("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'ActivityLog';");
+                dbContext.SaveChanges();
+
+                foreach (var entry in queryResult)
+                {
+                    var newEntry = new ActivityLog(
+                        entry[1].ToString(),
+                        entry[4].ToString(),
+                        entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString()),
+                        entry[7].ReadDateTime(),
+                        ParseLogLevel(entry[8].ToString()));
+
+                    if (entry[2].SQLiteType != SQLiteType.Null)
+                    {
+                        newEntry.Overview = entry[2].ToString();
+                    }
+
+                    if (entry[3].SQLiteType != SQLiteType.Null)
+                    {
+                        newEntry.ShortOverview = entry[3].ToString();
+                    }
+
+                    if (entry[5].SQLiteType != SQLiteType.Null)
+                    {
+                        newEntry.ItemId = entry[5].ToString();
+                    }
+
+                    dbContext.ActivityLogs.Add(newEntry);
+                    dbContext.SaveChanges();
+                }
+            }
+
+            try
+            {
+                File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
+            }
+            catch (IOException e)
+            {
+                logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'");
+            }
+        }
+
+        private LogLevel ParseLogLevel(string entry)
+        {
+            if (string.Equals(entry, "Debug", StringComparison.OrdinalIgnoreCase))
+            {
+                return LogLevel.Debug;
+            }
+
+            if (string.Equals(entry, "Information", StringComparison.OrdinalIgnoreCase)
+                || string.Equals(entry, "Info", StringComparison.OrdinalIgnoreCase))
+            {
+                return LogLevel.Information;
+            }
+
+            if (string.Equals(entry, "Warning", StringComparison.OrdinalIgnoreCase)
+                || string.Equals(entry, "Warn", StringComparison.OrdinalIgnoreCase))
+            {
+                return LogLevel.Warning;
+            }
+
+            if (string.Equals(entry, "Error", StringComparison.OrdinalIgnoreCase))
+            {
+                return LogLevel.Error;
+            }
+
+            return LogLevel.Trace;
+        }
+    }
+}

+ 6 - 5
MediaBrowser.Api/Library/LibraryService.cs

@@ -759,13 +759,14 @@ namespace MediaBrowser.Api.Library
         {
         {
             try
             try
             {
             {
-                _activityManager.Create(new ActivityLogEntry
+                _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog(
+                    string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
+                    "UserDownloadingContent",
+                    auth.UserId,
+                    DateTime.UtcNow,
+                    LogLevel.Trace)
                 {
                 {
-                    Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
-                    Type = "UserDownloadingContent",
                     ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
                     ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
-                    UserId = auth.UserId
-
                 });
                 });
             }
             }
             catch
             catch

+ 1 - 1
MediaBrowser.Api/System/ActivityLogService.cs

@@ -53,7 +53,7 @@ namespace MediaBrowser.Api.System
                 (DateTime?)null :
                 (DateTime?)null :
                 DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
                 DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
 
 
-            var result = _activityManager.GetActivityLogEntries(minDate, request.HasUserId, request.StartIndex, request.Limit);
+            var result = _activityManager.GetPagedResult(request.StartIndex, request.Limit);
 
 
             return ToOptimizedResult(result);
             return ToOptimizedResult(result);
         }
         }

+ 1 - 0
MediaBrowser.Model/Activity/ActivityLogEntry.cs

@@ -59,6 +59,7 @@ namespace MediaBrowser.Model.Activity
         /// Gets or sets the user primary image tag.
         /// Gets or sets the user primary image tag.
         /// </summary>
         /// </summary>
         /// <value>The user primary image tag.</value>
         /// <value>The user primary image tag.</value>
+        [Obsolete("UserPrimaryImageTag is not used.")]
         public string UserPrimaryImageTag { get; set; }
         public string UserPrimaryImageTag { get; set; }
 
 
         /// <summary>
         /// <summary>

+ 12 - 3
MediaBrowser.Model/Activity/IActivityManager.cs

@@ -1,6 +1,10 @@
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
 using System;
 using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 
 
@@ -10,10 +14,15 @@ namespace MediaBrowser.Model.Activity
     {
     {
         event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
         event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
 
 
-        void Create(ActivityLogEntry entry);
+        void Create(ActivityLog entry);
 
 
-        QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit);
+        Task CreateAsync(ActivityLog entry);
 
 
-        QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y);
+        QueryResult<ActivityLogEntry> GetPagedResult(int? startIndex, int? limit);
+
+        QueryResult<ActivityLogEntry> GetPagedResult(
+            Func<IQueryable<ActivityLog>, IEnumerable<ActivityLog>> func,
+            int? startIndex,
+            int? limit);
     }
     }
 }
 }

+ 0 - 14
MediaBrowser.Model/Activity/IActivityRepository.cs

@@ -1,14 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Model.Querying;
-
-namespace MediaBrowser.Model.Activity
-{
-    public interface IActivityRepository
-    {
-        void Create(ActivityLogEntry entry);
-
-        QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? z, int? startIndex, int? limit);
-    }
-}

+ 3 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -37,6 +37,9 @@
     <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
     <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
     <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
     <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
   </ItemGroup>
   </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" />
+  </ItemGroup>
 
 
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
     <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
     <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>

+ 24 - 22
MediaBrowser.sln

@@ -1,6 +1,6 @@
 Microsoft Visual Studio Solution File, Format Version 12.00
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26730.3
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30011.22
 MinimumVisualStudioVersion = 10.0.40219.1
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
 EndProject
 EndProject
@@ -46,23 +46,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}"
 EndProject
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}"
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}"
 EndProject
 EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -114,10 +116,6 @@ Global
 		{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU
 		{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU
-		{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU
 		{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -182,10 +180,22 @@ Global
 		{F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU
 		{F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
 	EndGlobalSection
 	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+		{28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+		{3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+		{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+		{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+		{462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
 		SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
 	EndGlobalSection
 	EndGlobalSection
@@ -207,12 +217,4 @@ Global
 		$0.DotNetNamingPolicy = $2
 		$0.DotNetNamingPolicy = $2
 		$2.DirectoryNamespaceAssociation = PrefixedHierarchical
 		$2.DirectoryNamespaceAssociation = PrefixedHierarchical
 	EndGlobalSection
 	EndGlobalSection
-	GlobalSection(NestedProjects) = preSolution
-		{DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
-		{28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
-		{3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
-		{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
-		{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
-		{462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
-	EndGlobalSection
 EndGlobal
 EndGlobal