From b670937c3d373173159a40f803e6e907ec0cd060 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 11 Dec 2020 15:00:43 -0700 Subject: [PATCH 01/45] Use typed UserManager GetPreference --- .../Library/UserViewManager.cs | 12 +++---- .../TV/TVSeriesManager.cs | 3 +- Jellyfin.Api/Controllers/ItemsController.cs | 16 ++++----- Jellyfin.Data/Entities/User.cs | 36 +++++++++++++++++-- .../Users/UserManager.cs | 18 +++++----- .../Migrations/Routines/MigrateUserDb.cs | 4 +-- MediaBrowser.Controller/Channels/Channel.cs | 8 ++--- .../Entities/Audio/MusicAlbum.cs | 2 +- .../Entities/Audio/MusicArtist.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 8 ++--- .../Entities/Movies/BoxSet.cs | 2 +- MediaBrowser.Controller/Entities/TV/Series.cs | 2 +- 12 files changed, 71 insertions(+), 42 deletions(-) diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index e4221dd508..6c74e6bd48 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -129,23 +129,23 @@ namespace Emby.Server.Implementations.Library if (!query.IncludeHidden) { - list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList(); + list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id)).ToList(); } var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); - var orders = user.GetPreference(PreferenceKind.OrderedViews).ToList(); + var orders = user.GetPreference(PreferenceKind.OrderedViews); return list .OrderBy(i => { - var index = orders.IndexOf(i.Id.ToString("D", CultureInfo.InvariantCulture)); + var index = Array.IndexOf(orders, i.Id); if (index == -1 && i is UserView view && view.DisplayParentId != Guid.Empty) { - index = orders.IndexOf(view.DisplayParentId.ToString("D", CultureInfo.InvariantCulture)); + index = Array.IndexOf(orders, view.DisplayParentId); } return index == -1 ? int.MaxValue : index; @@ -280,8 +280,8 @@ namespace Emby.Server.Implementations.Library { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) - .Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) + .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) + .Contains(i.Id)) .ToList(); } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 447c587f99..a2a9b37a75 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -75,8 +75,7 @@ namespace Emby.Server.Implementations.TV { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) - .Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) + .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes).Contains(i.Id)) .ToArray(); } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 7e9035f80f..0816efb38b 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -254,18 +254,18 @@ namespace Jellyfin.Api.Controllers includeItemTypes = new[] { "Playlist" }; } - bool isInEnabledFolder = user!.GetPreference(PreferenceKind.EnabledFolders).Any(i => new Guid(i) == item.Id) + var enabledChannels = user!.GetPreference(PreferenceKind.EnabledChannels); + + bool isInEnabledFolder = Array.IndexOf(user.GetPreference(PreferenceKind.EnabledFolders), item.Id) != -1 // Assume all folders inside an EnabledChannel are enabled - || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.Id) + || Array.IndexOf(enabledChannels, item.Id) != -1 // Assume all items inside an EnabledChannel are enabled - || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.ChannelId); + || Array.IndexOf(enabledChannels, item.ChannelId) != -1; var collectionFolders = _libraryManager.GetCollectionFolders(item); foreach (var collectionFolder in collectionFolders) { - if (user.GetPreference(PreferenceKind.EnabledFolders).Contains( - collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture), - StringComparer.OrdinalIgnoreCase)) + if (user.GetPreference(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id)) { isInEnabledFolder = true; } @@ -786,12 +786,12 @@ namespace Jellyfin.Api.Controllers var ancestorIds = Array.Empty(); - var excludeFolderIds = user.GetPreference(PreferenceKind.LatestItemExcludes); + var excludeFolderIds = user.GetPreference(PreferenceKind.LatestItemExcludes); if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) + .Where(i => !excludeFolderIds.Contains(i.Id)) .Select(i => i.Id) .ToArray(); } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 6d46819143..0733cc6705 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Globalization; @@ -413,6 +414,25 @@ namespace Jellyfin.Data.Entities return Equals(val, string.Empty) ? Array.Empty() : val.Split(Delimiter); } + /// + /// Gets the user's preferences for the given preference kind. + /// + /// The preference kind. + /// Type of preference. + /// A {T} array containing the user's preference. + public T[] GetPreference(PreferenceKind preference) + { + var val = Preferences.First(p => p.Kind == preference).Value; + if (string.IsNullOrEmpty(val)) + { + return Array.Empty(); + } + + var converter = TypeDescriptor.GetConverter(typeof(T)); + var stringValues = val.Split(Delimiter); + return Array.ConvertAll(stringValues, value => (T)converter.ConvertFromString(value)); + } + /// /// Sets the specified preference to the given value. /// @@ -421,7 +441,19 @@ namespace Jellyfin.Data.Entities public void SetPreference(PreferenceKind preference, string[] values) { Preferences.First(p => p.Kind == preference).Value - = string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values); + = string.Join(Delimiter, values); + } + + /// + /// Sets the specified preference to the given value. + /// + /// The preference kind. + /// The values. + /// The type of value. + public void SetPreference(PreferenceKind preference, T[] values) + { + Preferences.First(p => p.Kind == preference).Value + = string.Join(Delimiter, values); } /// @@ -441,7 +473,7 @@ namespace Jellyfin.Data.Entities /// True if the folder is in the user's grouped folders. public bool IsFolderGrouped(Guid id) { - return GetPreference(PreferenceKind.GroupedFolders).Any(i => new Guid(i) == id); + return Array.IndexOf(GetPreference(PreferenceKind.GroupedFolders), id) != -1; } private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index e37fcc908b..f576e662aa 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -376,14 +376,14 @@ namespace Jellyfin.Server.Implementations.Users EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), AccessSchedules = user.AccessSchedules.ToArray(), BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), - EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels)?.Select(Guid.Parse).ToArray(), + EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), - EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders)?.Select(Guid.Parse).ToArray(), + EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders), SyncPlayAccess = user.SyncPlayAccess, - BlockedChannels = user.GetPreference(PreferenceKind.BlockedChannels)?.Select(Guid.Parse).ToArray(), - BlockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders)?.Select(Guid.Parse).ToArray(), - BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems).Select(Enum.Parse).ToArray() + BlockedChannels = user.GetPreference(PreferenceKind.BlockedChannels), + BlockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders), + BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) } }; } @@ -704,13 +704,11 @@ namespace Jellyfin.Server.Implementations.Users } // TODO: fix this at some point - user.SetPreference( - PreferenceKind.BlockUnratedItems, - policy.BlockUnratedItems?.Select(i => i.ToString()).ToArray() ?? Array.Empty()); + user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty()); user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); - user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray()); + user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); - user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray()); + user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); dbContext.Update(user); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 74c5503314..33f039c394 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -168,9 +168,9 @@ namespace Jellyfin.Server.Migrations.Routines } user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); - user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray()); + user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); - user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray()); + user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews); user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index 129cdb519f..c8a8db528e 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -17,9 +17,10 @@ namespace MediaBrowser.Controller.Channels { public override bool IsVisible(User user) { - if (user.GetPreference(PreferenceKind.BlockedChannels) != null) + var blockedChannelsPreference = user.GetPreference(PreferenceKind.BlockedChannels); + if (blockedChannelsPreference.Length != 0) { - if (user.GetPreference(PreferenceKind.BlockedChannels).Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + if (blockedChannelsPreference.Contains(Id)) { return false; } @@ -27,8 +28,7 @@ namespace MediaBrowser.Controller.Channels else { if (!user.HasPermission(PermissionKind.EnableAllChannels) - && !user.GetPreference(PreferenceKind.EnabledChannels) - .Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + && !user.GetPreference(PreferenceKind.EnabledChannels).Contains(Id)) { return false; } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 48cd9371a0..260daa1c2e 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Entities.Audio protected override bool GetBlockUnratedValue(User user) { - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music.ToString()); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music); } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index c5e50cf45d..5d586bfb58 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -145,7 +145,7 @@ namespace MediaBrowser.Controller.Entities.Audio protected override bool GetBlockUnratedValue(User user) { - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music.ToString()); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music); } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 1b25fbdbb7..3aa93565bd 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -480,11 +480,11 @@ namespace MediaBrowser.Controller.Entities return true; } - var allowed = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders); + var allowed = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders); if (SourceType == SourceType.Channel) { - return allowed.Contains(ChannelId.ToString(""), StringComparer.OrdinalIgnoreCase); + return allowed.Contains(ChannelId); } else { @@ -492,7 +492,7 @@ namespace MediaBrowser.Controller.Entities foreach (var folder in collectionFolders) { - if (allowed.Contains(folder.Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + if (allowed.Contains(folder.Id)) { return true; } @@ -1909,7 +1909,7 @@ namespace MediaBrowser.Controller.Entities return false; } - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(GetBlockUnratedType().ToString()); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(GetBlockUnratedType()); } /// diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 8de88cc1b1..343da38256 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies protected override bool GetBlockUnratedValue(User user) { - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie.ToString()); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie); } public override double GetDefaultPrimaryImageAspectRatio() diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index e8afa9a490..aaed879c3a 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -452,7 +452,7 @@ namespace MediaBrowser.Controller.Entities.TV protected override bool GetBlockUnratedValue(User user) { - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series.ToString()); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series); } public override UnratedItem GetBlockUnratedType() From f5cce9e630135eec076a6cb9e7ea08d81bcd45c5 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 11 Dec 2020 15:04:14 -0700 Subject: [PATCH 02/45] Use typed UserManager GetPreference --- MediaBrowser.Controller/Entities/Folder.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 57d04ddfac..0f936538b3 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -186,13 +186,10 @@ namespace MediaBrowser.Controller.Entities { if (this is ICollectionFolder && !(this is BasePluginFolder)) { - var blockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders); + var blockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders); if (blockedMediaFolders.Length > 0) { - if (blockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || - - // Backwards compatibility - blockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) + if (blockedMediaFolders.Contains(Id)) { return false; } @@ -200,8 +197,7 @@ namespace MediaBrowser.Controller.Entities else { if (!user.HasPermission(PermissionKind.EnableAllFolders) - && !user.GetPreference(PreferenceKind.EnabledFolders) - .Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + && !user.GetPreference(PreferenceKind.EnabledFolders).Contains(Id)) { return false; } From 8f4a4a3cc56e9a5c0582c2bda3c0608daa781f64 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 12 Dec 2020 10:36:17 -0700 Subject: [PATCH 03/45] Convert values without throwing exception --- Jellyfin.Data/Entities/User.cs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 0733cc6705..d19cbac7fb 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; @@ -428,9 +427,36 @@ namespace Jellyfin.Data.Entities return Array.Empty(); } + // Convert array of {string} to array of {T} var converter = TypeDescriptor.GetConverter(typeof(T)); var stringValues = val.Split(Delimiter); - return Array.ConvertAll(stringValues, value => (T)converter.ConvertFromString(value)); + var parsedValues = new object[stringValues.Length]; + var convertedCount = 0; + for (var i = 0; i < stringValues.Length; i++) + { + try + { + parsedValues[i] = converter.ConvertFromString(stringValues[i].Trim()); + convertedCount++; + } + catch (FormatException) + { + // Unable to convert value + } + } + + var typedValues = new T[convertedCount]; + var typedValueIndex = 0; + for (var i = 0; i < parsedValues.Length; i++) + { + if (parsedValues[i] != null) + { + typedValues.SetValue(parsedValues[i], typedValueIndex); + typedValueIndex++; + } + } + + return typedValues; } /// From ee23d06154133ffc506dda3a8ef0e09d20c03c6c Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 13 Dec 2020 08:15:26 -0700 Subject: [PATCH 04/45] Use a more descriptive function name --- Emby.Server.Implementations/Library/UserViewManager.cs | 6 +++--- Emby.Server.Implementations/TV/TVSeriesManager.cs | 2 +- Jellyfin.Api/Controllers/ItemsController.cs | 8 ++++---- Jellyfin.Data/Entities/User.cs | 4 ++-- Jellyfin.Server.Implementations/Users/UserManager.cs | 10 +++++----- MediaBrowser.Controller/Channels/Channel.cs | 4 ++-- MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs | 2 +- MediaBrowser.Controller/Entities/Audio/MusicArtist.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 4 ++-- MediaBrowser.Controller/Entities/Folder.cs | 4 ++-- MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 2 +- MediaBrowser.Controller/Entities/TV/Series.cs | 2 +- 12 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 6c74e6bd48..b6b7ea9495 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -129,12 +129,12 @@ namespace Emby.Server.Implementations.Library if (!query.IncludeHidden) { - list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id)).ToList(); + list = list.Where(i => !user.GetPreferenceValues(PreferenceKind.MyMediaExcludes).Contains(i.Id)).ToList(); } var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); - var orders = user.GetPreference(PreferenceKind.OrderedViews); + var orders = user.GetPreferenceValues(PreferenceKind.OrderedViews); return list .OrderBy(i => @@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Library { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) + .Where(i => !user.GetPreferenceValues(PreferenceKind.LatestItemExcludes) .Contains(i.Id)) .ToList(); } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index a2a9b37a75..f0734340be 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.TV { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes).Contains(i.Id)) + .Where(i => !user.GetPreferenceValues(PreferenceKind.LatestItemExcludes).Contains(i.Id)) .ToArray(); } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 0816efb38b..b84136ac6f 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -254,9 +254,9 @@ namespace Jellyfin.Api.Controllers includeItemTypes = new[] { "Playlist" }; } - var enabledChannels = user!.GetPreference(PreferenceKind.EnabledChannels); + var enabledChannels = user!.GetPreferenceValues(PreferenceKind.EnabledChannels); - bool isInEnabledFolder = Array.IndexOf(user.GetPreference(PreferenceKind.EnabledFolders), item.Id) != -1 + bool isInEnabledFolder = Array.IndexOf(user.GetPreferenceValues(PreferenceKind.EnabledFolders), item.Id) != -1 // Assume all folders inside an EnabledChannel are enabled || Array.IndexOf(enabledChannels, item.Id) != -1 // Assume all items inside an EnabledChannel are enabled @@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers var collectionFolders = _libraryManager.GetCollectionFolders(item); foreach (var collectionFolder in collectionFolders) { - if (user.GetPreference(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id)) + if (user.GetPreferenceValues(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id)) { isInEnabledFolder = true; } @@ -786,7 +786,7 @@ namespace Jellyfin.Api.Controllers var ancestorIds = Array.Empty(); - var excludeFolderIds = user.GetPreference(PreferenceKind.LatestItemExcludes); + var excludeFolderIds = user.GetPreferenceValues(PreferenceKind.LatestItemExcludes); if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index d19cbac7fb..cc85a0b85d 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -419,7 +419,7 @@ namespace Jellyfin.Data.Entities /// The preference kind. /// Type of preference. /// A {T} array containing the user's preference. - public T[] GetPreference(PreferenceKind preference) + public T[] GetPreferenceValues(PreferenceKind preference) { var val = Preferences.First(p => p.Kind == preference).Value; if (string.IsNullOrEmpty(val)) @@ -499,7 +499,7 @@ namespace Jellyfin.Data.Entities /// True if the folder is in the user's grouped folders. public bool IsFolderGrouped(Guid id) { - return Array.IndexOf(GetPreference(PreferenceKind.GroupedFolders), id) != -1; + return Array.IndexOf(GetPreferenceValues(PreferenceKind.GroupedFolders), id) != -1; } private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index f576e662aa..400f02ef21 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -376,14 +376,14 @@ namespace Jellyfin.Server.Implementations.Users EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), AccessSchedules = user.AccessSchedules.ToArray(), BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), - EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), + EnabledChannels = user.GetPreferenceValues(PreferenceKind.EnabledChannels), EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), - EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), + EnabledFolders = user.GetPreferenceValues(PreferenceKind.EnabledFolders), EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders), SyncPlayAccess = user.SyncPlayAccess, - BlockedChannels = user.GetPreference(PreferenceKind.BlockedChannels), - BlockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders), - BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) + BlockedChannels = user.GetPreferenceValues(PreferenceKind.BlockedChannels), + BlockedMediaFolders = user.GetPreferenceValues(PreferenceKind.BlockedMediaFolders), + BlockUnratedItems = user.GetPreferenceValues(PreferenceKind.BlockUnratedItems) } }; } diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index c8a8db528e..b2315bda47 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Channels { public override bool IsVisible(User user) { - var blockedChannelsPreference = user.GetPreference(PreferenceKind.BlockedChannels); + var blockedChannelsPreference = user.GetPreferenceValues(PreferenceKind.BlockedChannels); if (blockedChannelsPreference.Length != 0) { if (blockedChannelsPreference.Contains(Id)) @@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Channels else { if (!user.HasPermission(PermissionKind.EnableAllChannels) - && !user.GetPreference(PreferenceKind.EnabledChannels).Contains(Id)) + && !user.GetPreferenceValues(PreferenceKind.EnabledChannels).Contains(Id)) { return false; } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 260daa1c2e..9a33ad9d74 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Entities.Audio protected override bool GetBlockUnratedValue(User user) { - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music); + return user.GetPreferenceValues(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music); } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 5d586bfb58..8a9bb12c7b 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -145,7 +145,7 @@ namespace MediaBrowser.Controller.Entities.Audio protected override bool GetBlockUnratedValue(User user) { - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music); + return user.GetPreferenceValues(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music); } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 3aa93565bd..cbb02aabd7 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -480,7 +480,7 @@ namespace MediaBrowser.Controller.Entities return true; } - var allowed = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders); + var allowed = user.GetPreferenceValues(PreferenceKind.EnableContentDeletionFromFolders); if (SourceType == SourceType.Channel) { @@ -1909,7 +1909,7 @@ namespace MediaBrowser.Controller.Entities return false; } - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(GetBlockUnratedType()); + return user.GetPreferenceValues(PreferenceKind.BlockUnratedItems).Contains(GetBlockUnratedType()); } /// diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 0f936538b3..cac5026f70 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -186,7 +186,7 @@ namespace MediaBrowser.Controller.Entities { if (this is ICollectionFolder && !(this is BasePluginFolder)) { - var blockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders); + var blockedMediaFolders = user.GetPreferenceValues(PreferenceKind.BlockedMediaFolders); if (blockedMediaFolders.Length > 0) { if (blockedMediaFolders.Contains(Id)) @@ -197,7 +197,7 @@ namespace MediaBrowser.Controller.Entities else { if (!user.HasPermission(PermissionKind.EnableAllFolders) - && !user.GetPreference(PreferenceKind.EnabledFolders).Contains(Id)) + && !user.GetPreferenceValues(PreferenceKind.EnabledFolders).Contains(Id)) { return false; } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 343da38256..05e4229cad 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies protected override bool GetBlockUnratedValue(User user) { - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie); + return user.GetPreferenceValues(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie); } public override double GetDefaultPrimaryImageAspectRatio() diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index aaed879c3a..1a379074dc 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -452,7 +452,7 @@ namespace MediaBrowser.Controller.Entities.TV protected override bool GetBlockUnratedValue(User user) { - return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series); + return user.GetPreferenceValues(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series); } public override UnratedItem GetBlockUnratedType() From c6b381db1098041fe975f9830728bf1f000ce71f Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 13 Dec 2020 08:32:33 -0700 Subject: [PATCH 05/45] Use request body for mapping xml channels --- Jellyfin.Api/Controllers/LiveTvController.cs | 11 ++------ .../Models/LiveTvDtos/SetChannelMappingDto.cs | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 56d4b3933e..6f2d432277 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1119,20 +1119,15 @@ namespace Jellyfin.Api.Controllers /// /// Set channel mappings. /// - /// Provider id. - /// Tuner channel id. - /// Provider channel id. + /// The set channel mapping dto. /// Created channel mapping returned. /// An containing the created channel mapping. [HttpPost("ChannelMappings")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> SetChannelMapping( - [FromQuery] string? providerId, - [FromQuery] string? tunerChannelId, - [FromQuery] string? providerChannelId) + public async Task> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto) { - return await _liveTvManager.SetChannelMapping(providerId, tunerChannelId, providerChannelId).ConfigureAwait(false); + return await _liveTvManager.SetChannelMapping(setChannelMappingDto.ProviderId, setChannelMappingDto.TunerChannelId, setChannelMappingDto.ProviderChannelId).ConfigureAwait(false); } /// diff --git a/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs b/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs new file mode 100644 index 0000000000..2ddaa89e8b --- /dev/null +++ b/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.LiveTvDtos +{ + /// + /// Set channel mapping dto. + /// + public class SetChannelMappingDto + { + /// + /// Gets or sets the provider id. + /// + [Required] + public string ProviderId { get; set; } = string.Empty; + + /// + /// Gets or sets the tuner channel id. + /// + [Required] + public string TunerChannelId { get; set; } = string.Empty; + + /// + /// Gets or sets the provider channel id. + /// + [Required] + public string ProviderChannelId { get; set; } = string.Empty; + } +} \ No newline at end of file From ca5f87c7ebd2a1dca0def67759b6cf991a0407bb Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 14 Dec 2020 07:20:16 -0700 Subject: [PATCH 06/45] Fix get provider id extension --- MediaBrowser.Model/Entities/ProviderIdsExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 1782b42e21..98097477c6 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Model.Entities } instance.ProviderIds.TryGetValue(name, out string? id); - return id; + return string.IsNullOrEmpty(id) ? null : id; } /// From a515ecbada489bb79e8bcce6fd1aabe6d81ce235 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 14 Dec 2020 07:53:56 -0700 Subject: [PATCH 07/45] Use range operator to get subarray --- Jellyfin.Data/Entities/User.cs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index cc85a0b85d..725531a7d8 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -430,14 +430,17 @@ namespace Jellyfin.Data.Entities // Convert array of {string} to array of {T} var converter = TypeDescriptor.GetConverter(typeof(T)); var stringValues = val.Split(Delimiter); - var parsedValues = new object[stringValues.Length]; var convertedCount = 0; + var parsedValues = new T[stringValues.Length]; for (var i = 0; i < stringValues.Length; i++) { try { - parsedValues[i] = converter.ConvertFromString(stringValues[i].Trim()); - convertedCount++; + var parsedValue = converter.ConvertFromString(stringValues[i].Trim()); + if (parsedValue != null) + { + parsedValues.SetValue(parsedValue, convertedCount++); + } } catch (FormatException) { @@ -445,18 +448,7 @@ namespace Jellyfin.Data.Entities } } - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < parsedValues.Length; i++) - { - if (parsedValues[i] != null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; + return parsedValues[..convertedCount]; } /// From e0510909040a05d2b3170634ac2ef85a52ae184d Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 14 Dec 2020 09:03:36 -0700 Subject: [PATCH 08/45] Use proper array setter --- Jellyfin.Data/Entities/User.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 725531a7d8..a0232e156d 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -439,7 +439,7 @@ namespace Jellyfin.Data.Entities var parsedValue = converter.ConvertFromString(stringValues[i].Trim()); if (parsedValue != null) { - parsedValues.SetValue(parsedValue, convertedCount++); + parsedValues[convertedCount++] = (T)parsedValue; } } catch (FormatException) From 9f6000d845d40b58da193d61b1cf0229d3a39ca1 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 15 Dec 2020 08:18:36 +0100 Subject: [PATCH 09/45] Add missing seasons during AfterMetadataRefresh --- .../TV/SeriesMetadataService.cs | 133 +++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index c8fc568a22..9679081975 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -1,10 +1,16 @@ #pragma warning disable CS1591 +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; @@ -13,14 +19,27 @@ namespace MediaBrowser.Providers.TV { public class SeriesMetadataService : MetadataService { + private readonly ILocalizationManager _localizationManager; + public SeriesMetadataService( IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + ILocalizationManager localizationManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) { + _localizationManager = localizationManager; + } + + /// + protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) + { + await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false); + + RemoveObsoleteSeasons(item); + await FillInMissingSeasonsAsync(item, cancellationToken).ConfigureAwait(false); } /// @@ -62,5 +81,117 @@ namespace MediaBrowser.Providers.TV targetItem.AirDays = sourceItem.AirDays; } } + + private void RemoveObsoleteSeasons(Series series) + { + // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in FillInMissingSeasonsAsync. + var physicalSeasonNumbers = new HashSet(); + var virtualSeasons = new List(); + foreach (var existingSeason in series.Children.OfType()) + { + if (existingSeason.LocationType != LocationType.Virtual && existingSeason.IndexNumber.HasValue) + { + physicalSeasonNumbers.Add(existingSeason.IndexNumber.Value); + } + else if (existingSeason.LocationType == LocationType.Virtual) + { + virtualSeasons.Add(existingSeason); + } + } + + foreach (var virtualSeason in virtualSeasons) + { + var seasonNumber = virtualSeason.IndexNumber; + // If there's a physical season with the same number or no episodes in the season, delete it + if ((seasonNumber.HasValue && physicalSeasonNumbers.Contains(seasonNumber.Value)) + || !virtualSeason.GetEpisodes().Any()) + { + Logger.LogInformation("Removing virtual season {SeasonNumber} in series {SeriesName}", virtualSeason.IndexNumber, series.Name); + + LibraryManager.DeleteItem( + virtualSeason, + new DeleteOptions + { + DeleteFileLocation = true + }, + false); + } + } + } + + /// + /// Creates seasons for all episodes that aren't in a season folder. + /// If no season number can be determined, a dummy season will be created. + /// + /// The series. + /// The cancellation token. + /// The async task. + private async Task FillInMissingSeasonsAsync(Series series, CancellationToken cancellationToken) + { + var episodesInSeriesFolder = series.GetRecursiveChildren(i => i is Episode) + .Cast() + .Where(i => !i.IsInSeasonFolder); + + List seasons = series.Children.OfType().ToList(); + + // Loop through the unique season numbers + foreach (var episode in episodesInSeriesFolder) + { + // Null season numbers will have a 'dummy' season created because seasons are always required. + var seasonNumber = episode.ParentIndexNumber >= 0 ? episode.ParentIndexNumber : null; + var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber); + + if (existingSeason == null) + { + var season = await CreateSeasonAsync(series, seasonNumber, cancellationToken).ConfigureAwait(false); + seasons.Add(season); + } + else if (existingSeason.IsVirtualItem) + { + existingSeason.IsVirtualItem = false; + await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); + } + } + } + + /// + /// Creates a new season, adds it to the database by linking it to the [series] and refreshes the metadata. + /// + /// The series. + /// The season number. + /// The cancellation token. + /// The newly created season. + private async Task CreateSeasonAsync( + Series series, + int? seasonNumber, + CancellationToken cancellationToken) + { + string seasonName = seasonNumber switch + { + null => _localizationManager.GetLocalizedString("NameSeasonUnknown"), + 0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName, + _ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value) + }; + + Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name); + + var season = new Season + { + Name = seasonName, + IndexNumber = seasonNumber, + Id = LibraryManager.GetNewItemId( + series.Id + (seasonNumber ?? -1).ToString(CultureInfo.InvariantCulture) + seasonName, + typeof(Season)), + IsVirtualItem = false, + SeriesId = series.Id, + SeriesName = series.Name + }; + + series.AddChild(season, cancellationToken); + + await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken).ConfigureAwait(false); + + return season; + } } } From 5b4eef741a0afc33a2f835a8feb3b0e600aaf47c Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 15 Dec 2020 09:27:33 +0100 Subject: [PATCH 10/45] Convert from base64 when saving item images --- Jellyfin.Api/Controllers/ImageController.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index e828a0801b..c606d327c5 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -325,9 +325,11 @@ namespace Jellyfin.Api.Controllers return NotFound(); } + await using var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); + // Handle image/png; charset=utf-8 var mimeType = Request.ContentType.Split(';').FirstOrDefault(); - await _providerManager.SaveImage(item, Request.Body, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(item, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); return NoContent(); @@ -358,9 +360,11 @@ namespace Jellyfin.Api.Controllers return NotFound(); } + await using var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); + // Handle image/png; charset=utf-8 var mimeType = Request.ContentType.Split(';').FirstOrDefault(); - await _providerManager.SaveImage(item, Request.Body, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(item, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); return NoContent(); From 1b3fcab6a400a9370b54287f4371638530a00e19 Mon Sep 17 00:00:00 2001 From: Ryan Petris Date: Tue, 15 Dec 2020 22:02:08 -0700 Subject: [PATCH 11/45] If requestContext is HttpRequest, get the context from it properly. --- .../HttpServer/Security/SessionContext.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 86914dea20..049291f7f6 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -45,6 +45,11 @@ namespace Emby.Server.Implementations.HttpServer.Security public User GetUser(object requestContext) { + if (requestContext is HttpRequest request) + { + return GetUser(request.HttpContext); + } + return GetUser((HttpContext)requestContext); } } From 875562e580a1f36d0364c04af22bfd9c7234ef95 Mon Sep 17 00:00:00 2001 From: Ryan Petris Date: Tue, 15 Dec 2020 22:17:04 -0700 Subject: [PATCH 12/45] This is only used in one place and therefore will always be HttpRequest. --- .../HttpServer/Security/SessionContext.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 049291f7f6..040b6b9e4e 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -45,12 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public User GetUser(object requestContext) { - if (requestContext is HttpRequest request) - { - return GetUser(request.HttpContext); - } - - return GetUser((HttpContext)requestContext); + return GetUser(((HttpRequest)requestContext).HttpContext); } } } From 7c3f2e7c599908bb4cdf027794950875efd5fb50 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 16 Dec 2020 21:20:29 +0800 Subject: [PATCH 13/45] Correct DLNA audio codecs for PS3 and PS4 * https://manuals.playstation.net/document/en/ps3/current/video/filetypes.html * https://manuals.playstation.net/document/en/ps4/music/mp_format_m.html --- Emby.Dlna/Profiles/SonyPs3Profile.cs | 4 ++-- Emby.Dlna/Profiles/SonyPs4Profile.cs | 4 ++-- Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml | 4 ++-- Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Dlna/Profiles/SonyPs3Profile.cs b/Emby.Dlna/Profiles/SonyPs3Profile.cs index d56b1df507..e4a7a3a596 100644 --- a/Emby.Dlna/Profiles/SonyPs3Profile.cs +++ b/Emby.Dlna/Profiles/SonyPs3Profile.cs @@ -52,7 +52,7 @@ namespace Emby.Dlna.Profiles Container = "ts,mpegts", Type = DlnaProfileType.Video, VideoCodec = "mpeg1video,mpeg2video,h264", - AudioCodec = "ac3,mp2,mp3,aac" + AudioCodec = "aac,ac3,mp2" }, new DirectPlayProfile { @@ -92,7 +92,7 @@ namespace Emby.Dlna.Profiles { Container = "ts", VideoCodec = "h264", - AudioCodec = "ac3,aac,mp3", + AudioCodec = "aac,ac3,mp2", Type = DlnaProfileType.Video }, new TranscodingProfile diff --git a/Emby.Dlna/Profiles/SonyPs4Profile.cs b/Emby.Dlna/Profiles/SonyPs4Profile.cs index db56094e2b..985df0c9a5 100644 --- a/Emby.Dlna/Profiles/SonyPs4Profile.cs +++ b/Emby.Dlna/Profiles/SonyPs4Profile.cs @@ -52,7 +52,7 @@ namespace Emby.Dlna.Profiles Container = "ts,mpegts", Type = DlnaProfileType.Video, VideoCodec = "mpeg1video,mpeg2video,h264", - AudioCodec = "ac3,mp2,mp3,aac" + AudioCodec = "aac,ac3,mp2" }, new DirectPlayProfile { @@ -94,7 +94,7 @@ namespace Emby.Dlna.Profiles { Container = "ts", VideoCodec = "h264", - AudioCodec = "mp3", + AudioCodec = "aac,ac3,mp2", Type = DlnaProfileType.Video }, new TranscodingProfile diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml index bafa44b828..129b188e2a 100644 --- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml +++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml @@ -38,7 +38,7 @@ - + @@ -46,7 +46,7 @@ - + diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml index eb8e645b31..592119305e 100644 --- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml +++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml @@ -38,7 +38,7 @@ - + @@ -46,7 +46,7 @@ - + From cf8aa37f5bd9bc76a78aac4e4232328f82fa4596 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 17 Dec 2020 23:33:44 +0800 Subject: [PATCH 14/45] Fix some video profile for Android client * Fix constrained high profile for some encoders * Extended profile is not supported by any known h264 encoders * Replace HEVC 10-bit profiles with main profile --- .../MediaEncoding/EncodingHelper.cs | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 91a03e6472..a8a094e63d 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1130,10 +1130,22 @@ namespace MediaBrowser.Controller.MediaEncoding var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault(); profile = Regex.Replace(profile, @"\s+", String.Empty); + // We only transcode to HEVC 8-bit for now, force Main Profile. + if (profile.Contains("main 10", StringComparison.OrdinalIgnoreCase) + || profile.Contains("main still", StringComparison.OrdinalIgnoreCase)) + { + profile = "main"; + } + + // Extended Profile is not supported by any known h264 encoders, force Main Profile. + if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase)) + { + profile = "main"; + } + // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile. if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) - && profile != null - && profile.IndexOf("high 10", StringComparison.OrdinalIgnoreCase) != -1) + && profile.Contains("high 10", StringComparison.OrdinalIgnoreCase)) { profile = "high"; } @@ -1141,8 +1153,7 @@ namespace MediaBrowser.Controller.MediaEncoding // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case, // which is compatible (and ugly). if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - && profile != null - && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1) + && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase)) { profile = "constrained_baseline"; } @@ -1151,16 +1162,24 @@ namespace MediaBrowser.Controller.MediaEncoding if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) - && profile != null - && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1) + && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase)) { profile = "baseline"; } + // libx264, h264_qsv, h264_nvenc and h264_vaapi does not support Constrained High profile, force High in this case. + if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + && profile.Contains("high", StringComparison.OrdinalIgnoreCase)) + { + profile = "high"; + } + // Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile. - if (!string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) - && profile != null - && profile.IndexOf("main 10", StringComparison.OrdinalIgnoreCase) != -1) + if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) + && profile.Contains("main 10", StringComparison.OrdinalIgnoreCase)) { profile = "main"; } From 9bf51aeb6b95272c278b15ad40d11e3837e9e8b2 Mon Sep 17 00:00:00 2001 From: nlspln Date: Thu, 17 Dec 2020 15:20:43 +0000 Subject: [PATCH 15/45] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index b6672a554a..1e80d0b5f9 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -1,7 +1,7 @@ { "Albums": "Albums", "AppDeviceValues": "App: {0}, Apparaat: {1}", - "Application": "Programma", + "Application": "Applicatie", "Artists": "Artiesten", "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", "Books": "Boeken", From e515b696d11fc0df34389ec5cb5f68e5c5eebde7 Mon Sep 17 00:00:00 2001 From: Miko Dela Cruz Date: Thu, 17 Dec 2020 15:41:50 +0000 Subject: [PATCH 16/45] Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- Emby.Server.Implementations/Localization/Core/ja.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index 02bf8496ff..fa0ab8b927 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -4,7 +4,7 @@ "Application": "アプリケーション", "Artists": "アーティスト", "AuthenticationSucceededWithUserName": "{0} 認証に成功しました", - "Books": "ブック", + "Books": "ブックス", "CameraImageUploadedFrom": "新しいカメライメージが {0}からアップロードされました", "Channels": "チャンネル", "ChapterNameValue": "チャプター {0}", @@ -114,5 +114,8 @@ "TaskRefreshChapterImages": "チャプター画像を抽出する", "TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする", "TaskCleanActivityLogDescription": "設定された期間よりも古いアクティビティの履歴を削除します。", - "TaskCleanActivityLog": "アクティビティの履歴を消去" + "TaskCleanActivityLog": "アクティビティの履歴を消去", + "Undefined": "未定義", + "Forced": "強制", + "Default": "デフォルト" } From 56ae63433f15c78aaebc2fe278829552fb8c5dd6 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 17 Dec 2020 13:05:44 -0700 Subject: [PATCH 17/45] Set filename when downloading file --- Jellyfin.Api/Controllers/LibraryController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 184843b397..c1538a4316 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -667,7 +667,7 @@ namespace Jellyfin.Api.Controllers } // TODO determine non-ASCII validity. - return PhysicalFile(path, MimeTypes.GetMimeType(path)); + return PhysicalFile(path, MimeTypes.GetMimeType(path), filename); } /// From be3129e012ab862bf80537a1668f32dda5db8554 Mon Sep 17 00:00:00 2001 From: SecularSteve Date: Thu, 17 Dec 2020 19:50:35 +0000 Subject: [PATCH 18/45] Translated using Weblate (English (United Kingdom)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en_GB/ --- Emby.Server.Implementations/Localization/Core/en-GB.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index cd64cdde45..7667612b90 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -115,5 +115,8 @@ "TasksLibraryCategory": "Library", "TasksMaintenanceCategory": "Maintenance", "TaskCleanActivityLogDescription": "Deletes activity log entries older than the configured age.", - "TaskCleanActivityLog": "Clean Activity Log" + "TaskCleanActivityLog": "Clean Activity Log", + "Undefined": "Undefined", + "Forced": "Forced", + "Default": "Default" } From ac03ef57c927ca27900b415ae45194e5d23be664 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sat, 19 Dec 2020 01:47:31 +0800 Subject: [PATCH 19/45] allow empty video encoder profile Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a8a094e63d..73ede7c5a4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1127,8 +1127,8 @@ namespace MediaBrowser.Controller.MediaEncoding targetVideoCodec = "hevc"; } - var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault(); - profile = Regex.Replace(profile, @"\s+", String.Empty); + var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty; + profile = Regex.Replace(profile, @"\s+", string.Empty); // We only transcode to HEVC 8-bit for now, force Main Profile. if (profile.Contains("main 10", StringComparison.OrdinalIgnoreCase) From 07ee98b65f3933770aedca3cf995d1dc0c3f39e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Predrag=20Ljubenovi=C4=87?= Date: Fri, 18 Dec 2020 16:42:21 +0000 Subject: [PATCH 20/45] Translated using Weblate (Serbian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/ --- Emby.Server.Implementations/Localization/Core/sr.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index 8da92f3099..2984ed972e 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -114,5 +114,8 @@ "TasksLibraryCategory": "Библиотека", "TasksMaintenanceCategory": "Одржавање", "TaskCleanActivityLogDescription": "Брише историју активности старију од конфигурисаног броја година.", - "TaskCleanActivityLog": "Очисти историју активности" + "TaskCleanActivityLog": "Очисти историју активности", + "Undefined": "Недефинисано", + "Forced": "Форсирано", + "Default": "Подразумевано" } From f06d52c4756fa2b61744fb11d7cf105b349a1885 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 23:29:21 +0000 Subject: [PATCH 21/45] Disable API if dlna is disabled. --- Emby.Dlna/Main/DlnaEntryPoint.cs | 6 +++ .../Controllers/DlnaServerController.cs | 39 +++++++++++++++---- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index fb4454a343..175a1ad2b1 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -126,6 +126,11 @@ namespace Emby.Dlna.Main public static DlnaEntryPoint Current { get; private set; } + /// + /// Gets a value indicating whether the dlna server is enabled. + /// + public static bool Enabled { get; private set; } + public IContentDirectory ContentDirectory { get; private set; } public IConnectionManager ConnectionManager { get; private set; } @@ -152,6 +157,7 @@ namespace Emby.Dlna.Main private void ReloadComponents() { var options = _config.GetDlnaConfiguration(); + Enabled = options.EnableServer; StartSsdpHandler(); diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index 4fd9c2fbf6..8555cbfa6b 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -45,14 +45,20 @@ namespace Jellyfin.Api.Controllers [HttpGet("{serverId}/description")] [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult GetDescriptionXml([FromRoute, Required] string serverId) { - var url = GetAbsoluteUri(); - var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); - var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress); - return Ok(xml); + if (DlnaEntryPoint.Enabled) + { + var url = GetAbsoluteUri(); + var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); + var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress); + return Ok(xml); + } + + return NotFound(); } /// @@ -65,12 +71,18 @@ namespace Jellyfin.Api.Controllers [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")] [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] public ActionResult GetContentDirectory([FromRoute, Required] string serverId) { - return Ok(_contentDirectory.GetServiceXml()); + if (DlnaEntryPoint.Enabled) + { + return Ok(_contentDirectory.GetServiceXml()); + } + + return NotFound(); } /// @@ -83,12 +95,18 @@ namespace Jellyfin.Api.Controllers [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")] [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId) { - return Ok(_mediaReceiverRegistrar.GetServiceXml()); + if (DlnaEntryPoint.Enabled) + { + return Ok(_mediaReceiverRegistrar.GetServiceXml()); + } + + return NotFound(); } /// @@ -101,12 +119,18 @@ namespace Jellyfin.Api.Controllers [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")] [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] public ActionResult GetConnectionManager([FromRoute, Required] string serverId) { - return Ok(_connectionManager.GetServiceXml()); + if (DlnaEntryPoint.Enabled) + { + return Ok(_connectionManager.GetServiceXml()); + } + + return NotFound(); } /// @@ -147,6 +171,7 @@ namespace Jellyfin.Api.Controllers /// Control response. [HttpPost("{serverId}/MediaReceiverRegistrar/Control")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public async Task> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId) From c98144c60d25b2ccb6f6ad3e2e2b1b04f88067ab Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 18 Dec 2020 23:59:21 +0000 Subject: [PATCH 22/45] Updated docs. --- Jellyfin.Api/Controllers/DlnaServerController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index 8555cbfa6b..914add9f12 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -41,6 +41,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Description xml returned. + /// Not found. /// An containing the description xml. [HttpGet("{serverId}/description")] [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")] @@ -66,6 +67,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna content directory returned. + /// Not found. /// An containing the dlna content directory xml. [HttpGet("{serverId}/ContentDirectory")] [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")] @@ -90,6 +92,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna media receiver registrar xml returned. + /// Not found. /// Dlna media receiver registrar xml. [HttpGet("{serverId}/MediaReceiverRegistrar")] [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")] @@ -114,6 +117,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna media receiver registrar xml returned. + /// Not found. /// Dlna media receiver registrar xml. [HttpGet("{serverId}/ConnectionManager")] [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")] @@ -168,6 +172,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. + /// Not found. /// Control response. [HttpPost("{serverId}/MediaReceiverRegistrar/Control")] [ProducesResponseType(StatusCodes.Status200OK)] From 3d0b9f9ea14a4ffae2daffb454162fe05d626b14 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 19 Dec 2020 09:34:04 +0000 Subject: [PATCH 23/45] Did the other API, --- .../Controllers/DlnaServerController.cs | 67 ++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index 914add9f12..42658bb8c7 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -145,11 +145,17 @@ namespace Jellyfin.Api.Controllers /// Control response. [HttpPost("{serverId}/ContentDirectory/Control")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public async Task> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId) { - return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false); + if (DlnaEntryPoint.Enabled) + { + return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false); + } + + return NotFound(); } /// @@ -160,11 +166,17 @@ namespace Jellyfin.Api.Controllers /// Control response. [HttpPost("{serverId}/ConnectionManager/Control")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public async Task> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId) { - return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false); + if (DlnaEntryPoint.Enabled) + { + return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false); + } + + return NotFound(); } /// @@ -181,7 +193,12 @@ namespace Jellyfin.Api.Controllers [ProducesFile(MediaTypeNames.Text.Xml)] public async Task> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId) { - return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); + if (DlnaEntryPoint.Enabled) + { + return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); + } + + return NotFound(); } /// @@ -195,11 +212,17 @@ namespace Jellyfin.Api.Controllers [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessMediaReceiverRegistrarEventRequest(string serverId) { - return ProcessEventRequest(_mediaReceiverRegistrar); + if (DlnaEntryPoint.Enabled) + { + return ProcessEventRequest(_mediaReceiverRegistrar); + } + + return NotFound(); } /// @@ -207,17 +230,24 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. + /// Not found. /// Event subscription response. [HttpSubscribe("{serverId}/ContentDirectory/Events")] [HttpUnsubscribe("{serverId}/ContentDirectory/Events")] [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessContentDirectoryEventRequest(string serverId) { - return ProcessEventRequest(_contentDirectory); + if (DlnaEntryPoint.Enabled) + { + return ProcessEventRequest(_contentDirectory); + } + + return NotFound(); } /// @@ -225,17 +255,24 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. + /// Not found. /// Event subscription response. [HttpSubscribe("{serverId}/ConnectionManager/Events")] [HttpUnsubscribe("{serverId}/ConnectionManager/Events")] [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessConnectionManagerEventRequest(string serverId) { - return ProcessEventRequest(_connectionManager); + if (DlnaEntryPoint.Enabled) + { + return ProcessEventRequest(_connectionManager); + } + + return NotFound(); } /// @@ -243,14 +280,21 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// The icon filename. + /// Not found. /// Icon stream. [HttpGet("{serverId}/icons/{fileName}")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesImageFile] public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName) { - return GetIconInternal(fileName); + if (DlnaEntryPoint.Enabled) + { + return GetIconInternal(fileName); + } + + return NotFound(); } /// @@ -258,11 +302,18 @@ namespace Jellyfin.Api.Controllers /// /// The icon filename. /// Icon stream. + /// Not found. [HttpGet("icons/{fileName}")] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesImageFile] public ActionResult GetIcon([FromRoute, Required] string fileName) { - return GetIconInternal(fileName); + if (DlnaEntryPoint.Enabled) + { + return GetIconInternal(fileName); + } + + return NotFound(); } private ActionResult GetIconInternal(string fileName) From 94707121186f1b71c21a0977bef7c8ab746932df Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 19 Dec 2020 17:38:46 +0000 Subject: [PATCH 24/45] Added xml docs --- Jellyfin.Api/Controllers/DlnaServerController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index 42658bb8c7..a3c82ed847 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -142,6 +142,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. + /// Not found. /// Control response. [HttpPost("{serverId}/ContentDirectory/Control")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -163,6 +164,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. + /// Not found. /// Control response. [HttpPost("{serverId}/ConnectionManager/Control")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -206,6 +208,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. + /// Not found. /// Event subscription response. [HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")] [HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")] From 4539164d3892701d6d358c558df5359b282a3568 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 19 Dec 2020 10:55:07 -0700 Subject: [PATCH 25/45] Add request parameters to OpenLiveStreamDto --- .../Controllers/MediaInfoController.cs | 28 +++++----- .../Models/MediaInfoDtos/OpenLiveStreamDto.cs | 55 +++++++++++++++++++ 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index 2a1da31c9e..baa2e06361 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -259,24 +259,24 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? subtitleStreamIndex, [FromQuery] int? maxAudioChannels, [FromQuery] Guid? itemId, - [FromBody] OpenLiveStreamDto openLiveStreamDto, - [FromQuery] bool enableDirectPlay = true, - [FromQuery] bool enableDirectStream = true) + [FromBody] OpenLiveStreamDto? openLiveStreamDto, + [FromQuery] bool? enableDirectPlay, + [FromQuery] bool? enableDirectStream) { var request = new LiveStreamRequest { - OpenToken = openToken, - UserId = userId ?? Guid.Empty, - PlaySessionId = playSessionId, - MaxStreamingBitrate = maxStreamingBitrate, - StartTimeTicks = startTimeTicks, - AudioStreamIndex = audioStreamIndex, - SubtitleStreamIndex = subtitleStreamIndex, - MaxAudioChannels = maxAudioChannels, - ItemId = itemId ?? Guid.Empty, + OpenToken = openToken ?? openLiveStreamDto?.OpenToken, + UserId = userId ?? openLiveStreamDto?.UserId ?? Guid.Empty, + PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId, + MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate, + StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks, + AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex, + SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex, + MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels, + ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty, DeviceProfile = openLiveStreamDto?.DeviceProfile, - EnableDirectPlay = enableDirectPlay, - EnableDirectStream = enableDirectStream, + EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true, + EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true, DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http } }; return await _mediaInfoHelper.OpenMediaSource(Request, request).ConfigureAwait(false); diff --git a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs index b0b3de8553..7045423261 100644 --- a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs +++ b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs @@ -10,6 +10,61 @@ namespace Jellyfin.Api.Models.MediaInfoDtos /// public class OpenLiveStreamDto { + /// + /// Gets or sets the open token. + /// + public string? OpenToken { get; set; } + + /// + /// Gets or sets the user id. + /// + public Guid? UserId { get; set; } + + /// + /// Gets or sets the play session id. + /// + public string? PlaySessionId { get; set; } + + /// + /// Gets or sets the max streaming bitrate. + /// + public int? MaxStreamingBitrate { get; set; } + + /// + /// Gets or sets the start time in ticks. + /// + public long? StartTimeTicks { get; set; } + + /// + /// Gets or sets the audio stream index. + /// + public int? AudioStreamIndex { get; set; } + + /// + /// Gets or sets the subtitle stream index. + /// + public int? SubtitleStreamIndex { get; set; } + + /// + /// Gets or sets the max audio channels. + /// + public int? MaxAudioChannels { get; set; } + + /// + /// Gets or sets the item id. + /// + public Guid? ItemId { get; set; } + + /// + /// Gets or sets a value indicating whether to enable direct play. + /// + public bool? EnableDirectPlay { get; set; } + + /// + /// Gets or sets a value indicating whether to enale direct stream. + /// + public bool? EnableDirectStream { get; set; } + /// /// Gets or sets the device profile. /// From 841df64e9f4399304337d9303b5c2a5b015d996a Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 19 Dec 2020 15:56:08 -0500 Subject: [PATCH 26/45] Add static Linux builds for arm and musl --- deployment/Dockerfile.linux.amd64-musl | 31 ++++++++++++++++++++++++++ deployment/Dockerfile.linux.arm64 | 31 ++++++++++++++++++++++++++ deployment/Dockerfile.linux.armhf | 31 ++++++++++++++++++++++++++ deployment/build.linux.amd64-musl | 31 ++++++++++++++++++++++++++ deployment/build.linux.arm64 | 31 ++++++++++++++++++++++++++ deployment/build.linux.armhf | 31 ++++++++++++++++++++++++++ 6 files changed, 186 insertions(+) create mode 100644 deployment/Dockerfile.linux.amd64-musl create mode 100644 deployment/Dockerfile.linux.arm64 create mode 100644 deployment/Dockerfile.linux.armhf create mode 100755 deployment/build.linux.amd64-musl create mode 100755 deployment/build.linux.arm64 create mode 100755 deployment/build.linux.armhf diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl new file mode 100644 index 0000000000..08b4ffa528 --- /dev/null +++ b/deployment/Dockerfile.linux.amd64-musl @@ -0,0 +1,31 @@ +FROM debian:10 +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=5.0 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=amd64 +ENV IS_DOCKER=YES + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Link to docker-build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.amd64-musl /build.sh + +VOLUME ${SOURCE_DIR}/ + +VOLUME ${ARTIFACT_DIR}/ + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64 new file mode 100644 index 0000000000..b8499c9172 --- /dev/null +++ b/deployment/Dockerfile.linux.arm64 @@ -0,0 +1,31 @@ +FROM debian:10 +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=5.0 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=arm64 +ENV IS_DOCKER=YES + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Link to docker-build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.arm64 /build.sh + +VOLUME ${SOURCE_DIR}/ + +VOLUME ${ARTIFACT_DIR}/ + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf new file mode 100644 index 0000000000..80c4d14693 --- /dev/null +++ b/deployment/Dockerfile.linux.armhf @@ -0,0 +1,31 @@ +FROM debian:10 +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=5.0 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=armhf +ENV IS_DOCKER=YES + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Link to docker-build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.armhf /build.sh + +VOLUME ${SOURCE_DIR}/ + +VOLUME ${ARTIFACT_DIR}/ + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/build.linux.amd64-musl b/deployment/build.linux.amd64-musl new file mode 100755 index 0000000000..72444c05e7 --- /dev/null +++ b/deployment/build.linux.amd64-musl @@ -0,0 +1,31 @@ +#!/bin/bash + +#= Generic Linux amd64-musl .tar.gz + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Get version +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi + +# Build archives +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" +tar -czf jellyfin-server_${version}_linux-amd64-musl.tar.gz -C dist jellyfin-server_${version} +rm -rf dist/jellyfin-server_${version} + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/ +mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.linux.arm64 b/deployment/build.linux.arm64 new file mode 100755 index 0000000000..e362607a76 --- /dev/null +++ b/deployment/build.linux.arm64 @@ -0,0 +1,31 @@ +#!/bin/bash + +#= Generic Linux arm64 .tar.gz + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Get version +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi + +# Build archives +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" +tar -czf jellyfin-server_${version}_linux-arm64.tar.gz -C dist jellyfin-server_${version} +rm -rf dist/jellyfin-server_${version} + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/ +mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.linux.armhf b/deployment/build.linux.armhf new file mode 100755 index 0000000000..c0d0607fc7 --- /dev/null +++ b/deployment/build.linux.armhf @@ -0,0 +1,31 @@ +#!/bin/bash + +#= Generic Linux armhf .tar.gz + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Get version +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi + +# Build archives +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" +tar -czf jellyfin-server_${version}_linux-armhf.tar.gz -C dist jellyfin-server_${version} +rm -rf dist/jellyfin-server_${version} + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/ +mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd From 704d627f3a14b879a6e2fae363474a308079c8bb Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 19 Dec 2020 15:57:08 -0500 Subject: [PATCH 27/45] Add Azure pipeline configs for new arches --- .ci/azure-pipelines-package.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 47477ba602..6063851163 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -22,6 +22,12 @@ jobs: BuildConfiguration: ubuntu.armhf Linux.amd64: BuildConfiguration: linux.amd64 + Linux.amd64-musl: + BuildConfiguration: linux.amd64-musl + Linux.arm64: + BuildConfiguration: linux.arm64 + Linux.armhf: + BuildConfiguration: linux.armhf Windows.amd64: BuildConfiguration: windows.amd64 MacOS: From b8ef0823fac3984296562f5cc31e7f740fc28ff2 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 19 Dec 2020 19:20:24 +0000 Subject: [PATCH 28/45] Translated using Weblate (Kazakh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- Emby.Server.Implementations/Localization/Core/kk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 91c1fb15bc..a092614fd0 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -91,5 +91,6 @@ "UserStoppedPlayingItemWithValues": "{0} - {1} oınatýyn {2} toqtatty", "ValueHasBeenAddedToLibrary": "{0} (tasyǵyshhanaǵa ústelindi)", "ValueSpecialEpisodeName": "Arnaıy - {0}", - "VersionNumber": "Nusqasy {0}" + "VersionNumber": "Nusqasy {0}", + "Default": "Ádepki" } From 1d50c2ad63fd0e312d1b0190d5caaff9444df76c Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sun, 20 Dec 2020 06:00:44 +0000 Subject: [PATCH 29/45] Translated using Weblate (Kazakh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- .../Localization/Core/kk.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index a092614fd0..47c0879be4 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -92,5 +92,21 @@ "ValueHasBeenAddedToLibrary": "{0} (tasyǵyshhanaǵa ústelindi)", "ValueSpecialEpisodeName": "Arnaıy - {0}", "VersionNumber": "Nusqasy {0}", - "Default": "Ádepki" + "Default": "Ádepki", + "TaskDownloadMissingSubtitles": "Joq sýbtıtrlerdi júktep alý", + "TaskRefreshChannels": "Arnalardy jańartý", + "TaskCleanTranscode": "Qaıta kodtaý katalogyn tazalaý", + "TaskUpdatePlugins": "Plagınderdi jańartý", + "TaskRefreshPeople": "Adamdardy jańartý", + "TaskCleanLogs": "Jurnal katalogyn tazalaý", + "TaskRefreshLibrary": "Tasyǵyshhanany skanerleý", + "TaskRefreshChapterImages": "Sahna keskinderin shyǵaryp alý", + "TaskCleanCache": "Kesh katalogyn tazalaý", + "TaskCleanActivityLog": "Áreket jurnalyn tazalaý", + "TasksChannelsCategory": "Internet-arnalar", + "TasksApplicationCategory": "Qoldanba", + "TasksLibraryCategory": "Tasyǵyshhana", + "TasksMaintenanceCategory": "Qyzmet kórsetý", + "Undefined": "Anyqtalmady", + "Forced": "Májbúrli" } From cc5d0af4c18cc2632f1a77a53d3bdef0be3d903b Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sun, 20 Dec 2020 06:12:52 +0000 Subject: [PATCH 30/45] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/ --- Emby.Server.Implementations/Localization/Core/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index ca6172fce0..03d30247a7 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -96,7 +96,7 @@ "TaskRefreshChannels": "Обновление каналов", "TaskCleanTranscode": "Очистка каталога перекодировки", "TaskUpdatePlugins": "Обновление плагинов", - "TaskRefreshPeople": "Обновление метаданных людей", + "TaskRefreshPeople": "Подновить людей", "TaskCleanLogs": "Очистка каталога журналов", "TaskRefreshLibrary": "Сканирование медиатеки", "TaskRefreshChapterImages": "Извлечение изображений сцен", @@ -109,7 +109,7 @@ "TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.", "TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.", "TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.", - "TaskRefreshPeopleDescription": "Обновляются метаданные актеров и режиссёров в медиатеке.", + "TaskRefreshPeopleDescription": "Обновляются метаданные для актёров и режиссёров в медиатеке.", "TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).", "TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.", "TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.", From f4d1e33010d1b3577fbec19ad9c8b70950ce33dd Mon Sep 17 00:00:00 2001 From: David Date: Sun, 20 Dec 2020 15:35:10 +0100 Subject: [PATCH 31/45] Fix similar items endpoint for movies and TV --- Jellyfin.Api/Controllers/LibraryController.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index c1538a4316..0b90eeea7a 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -732,9 +732,6 @@ namespace Jellyfin.Api.Controllers } else { - // For non series and movie types these columns are typically null - isSeries = null; - isMovie = null; includeItemTypes.Add(item.GetType().Name); } @@ -742,8 +739,6 @@ namespace Jellyfin.Api.Controllers { Limit = limit, IncludeItemTypes = includeItemTypes.ToArray(), - IsMovie = isMovie, - IsSeries = isSeries, SimilarTo = item, DtoOptions = dtoOptions, EnableTotalRecordCount = !isMovie ?? true, From eba403158e1769a3a5e7e49e0e96f0af2a96d14e Mon Sep 17 00:00:00 2001 From: David Date: Sun, 20 Dec 2020 17:00:27 +0100 Subject: [PATCH 32/45] Re-add IsMovie --- Jellyfin.Api/Controllers/LibraryController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 0b90eeea7a..deb7052aa3 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -732,6 +732,9 @@ namespace Jellyfin.Api.Controllers } else { + // For non series and movie types these columns are typically null + isSeries = null; + isMovie = null; includeItemTypes.Add(item.GetType().Name); } @@ -739,6 +742,7 @@ namespace Jellyfin.Api.Controllers { Limit = limit, IncludeItemTypes = includeItemTypes.ToArray(), + IsMovie = isMovie, SimilarTo = item, DtoOptions = dtoOptions, EnableTotalRecordCount = !isMovie ?? true, From a682fc4516eea7076114c7f24f47401117d3e423 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 20 Dec 2020 12:59:44 -0700 Subject: [PATCH 33/45] Return dashboardTheme when requesting DisplayPreferences --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index f7bb968f0b..87b4577b62 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -85,6 +85,7 @@ namespace Jellyfin.Api.Controllers dto.CustomPrefs["skipBackLength"] = displayPreferences.SkipBackwardLength.ToString(CultureInfo.InvariantCulture); dto.CustomPrefs["enableNextVideoInfoOverlay"] = displayPreferences.EnableNextVideoInfoOverlay.ToString(CultureInfo.InvariantCulture); dto.CustomPrefs["tvhome"] = displayPreferences.TvHome; + dto.CustomPrefs["dashboardTheme"] = displayPreferences.DashboardTheme; // Load all custom display preferences var customDisplayPreferences = _displayPreferencesManager.ListCustomItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client); From 13de66320126b23f4bb585e5cea3946fa010ce52 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 20 Dec 2020 22:21:11 +0100 Subject: [PATCH 34/45] Fix similar endpoint for TV --- Jellyfin.Api/Controllers/LibraryController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index deb7052aa3..28d359ac3a 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -742,7 +742,6 @@ namespace Jellyfin.Api.Controllers { Limit = limit, IncludeItemTypes = includeItemTypes.ToArray(), - IsMovie = isMovie, SimilarTo = item, DtoOptions = dtoOptions, EnableTotalRecordCount = !isMovie ?? true, From 3df97d54418058d933d7a260fb201a2b1fd5ed40 Mon Sep 17 00:00:00 2001 From: Page Asgardius Date: Mon, 21 Dec 2020 00:36:24 +0000 Subject: [PATCH 35/45] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/ --- Emby.Server.Implementations/Localization/Core/es_419.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json index dcd30694f0..03c6d5f5d3 100644 --- a/Emby.Server.Implementations/Localization/Core/es_419.json +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -112,5 +112,9 @@ "CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}", "AuthenticationSucceededWithUserName": "{0} autenticado con éxito", "Application": "Aplicación", - "AppDeviceValues": "App: {0}, Dispositivo: {1}" + "AppDeviceValues": "App: {0}, Dispositivo: {1}", + "TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.", + "TaskCleanActivityLog": "Limpiar Registro de Actividades", + "Undefined": "Sin definir", + "Forced": "Forzado" } From e778fda843c7ea1a746ea93472e661ce5438dfdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Predrag=20Ljubenovi=C4=87?= Date: Sun, 20 Dec 2020 20:15:53 +0000 Subject: [PATCH 36/45] Translated using Weblate (Serbian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/ --- .../Localization/Core/sr.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index 2984ed972e..d785bcb907 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -66,7 +66,7 @@ "Inherit": "Наследи", "HomeVideos": "Кућни видео", "HeaderRecordingGroups": "Групе снимања", - "HeaderNextUp": "Следеће горе", + "HeaderNextUp": "Следи", "HeaderLiveTV": "ТВ уживо", "HeaderFavoriteSongs": "Омиљене песме", "HeaderFavoriteShows": "Омиљене серије", @@ -79,17 +79,17 @@ "Folders": "Фасцикле", "Favorites": "Омиљено", "FailedLoginAttemptWithUserName": "Неуспела пријава са {0}", - "DeviceOnlineWithName": "{0} се повезао", + "DeviceOnlineWithName": "{0} је повезан", "DeviceOfflineWithName": "{0} је прекинуо везу", "Collections": "Колекције", "ChapterNameValue": "Поглавље {0}", "Channels": "Канали", - "CameraImageUploadedFrom": "Нова фотографија је послата са {0}", + "CameraImageUploadedFrom": "Нова фотографија је учитана са {0}", "Books": "Књиге", "AuthenticationSucceededWithUserName": "{0} успешно проверено", - "Artists": "Извођач", + "Artists": "Извођачи", "Application": "Апликација", - "AppDeviceValues": "Апл: {0}, уређај: {1}", + "AppDeviceValues": "Апликација: {0}, Уређај: {1}", "Albums": "Албуми", "TaskDownloadMissingSubtitlesDescription": "Претражује интернет за недостајуће титлове на основу конфигурације метаподатака.", "TaskDownloadMissingSubtitles": "Преузмите недостајуће титлове", @@ -104,7 +104,7 @@ "TaskCleanLogsDescription": "Брише логове старије од {0} дана.", "TaskCleanLogs": "Очистите директоријум логова", "TaskRefreshLibraryDescription": "Скенира вашу медијску библиотеку за нове датотеке и освежава метаподатке.", - "TaskRefreshLibrary": "Скенирај Библиотеку Медија", + "TaskRefreshLibrary": "Скенирај библиотеку медија", "TaskRefreshChapterImagesDescription": "Ствара сличице за видео записе који имају поглавља.", "TaskRefreshChapterImages": "Издвоји слике из поглавља", "TaskCleanCacheDescription": "Брише Кеш фајлове који више нису потребни систему.", From 242b5a45ab198ba4ddcfa7cc9adb1c2217b455c7 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 21 Dec 2020 08:12:32 -0700 Subject: [PATCH 37/45] Add JsonDateTimeConverter --- .../Json/Converters/JsonDateTimeConverter.cs | 34 +++++++++++++++++++ MediaBrowser.Common/Json/JsonDefaults.cs | 1 + 2 files changed, 35 insertions(+) create mode 100644 MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs diff --git a/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs b/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs new file mode 100644 index 0000000000..698b161c99 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Legacy DateTime converter. + /// Milliseconds aren't output if zero by default. + /// + public class JsonDateTimeConverter : JsonConverter + { + /// + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDateTime(); + } + + /// + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + if (value.Ticks % 10_000_000 == 0) + { + // Remaining ticks value will be 0, manually format. + writer.WriteStringValue(value.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffZ", CultureInfo.InvariantCulture)); + } + else + { + writer.WriteStringValue(value); + } + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index b76edd2bc7..128cc9d5d0 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -44,6 +44,7 @@ namespace MediaBrowser.Common.Json options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new JsonNullableStructConverterFactory()); options.Converters.Add(new JsonBoolNumberConverter()); + options.Converters.Add(new JsonDateTimeConverter()); return options; } From d3b6a24f78d7a70fc61fe0d479be4b5d3d57bd3f Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 21 Dec 2020 08:41:13 -0700 Subject: [PATCH 38/45] Add JsonDateTimeConverter --- MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs b/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs index 698b161c99..73e3a0493d 100644 --- a/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Common.Json.Converters /// public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { - if (value.Ticks % 10_000_000 == 0) + if (value.Millisecond == 0) { // Remaining ticks value will be 0, manually format. writer.WriteStringValue(value.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffZ", CultureInfo.InvariantCulture)); From 3a6501abe0d2ea4b7b8f19bfffd95467fce555b8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 21 Dec 2020 17:28:06 -0700 Subject: [PATCH 39/45] Fix another key collision in MigrateDisplayPreferencesDatabase --- .../Routines/MigrateDisplayPreferencesDb.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index dd005b7f47..f4040748d7 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -8,7 +8,6 @@ using System.Text.Json.Serialization; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Server.Implementations; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; @@ -81,7 +80,8 @@ namespace Jellyfin.Server.Migrations.Routines { "unstable", ChromecastVersion.Unstable } }; - var customDisplayPrefs = new HashSet(); + var displayPrefs = new HashSet(StringComparer.OrdinalIgnoreCase); + var customDisplayPrefs = new HashSet(StringComparer.OrdinalIgnoreCase); var dbFilePath = Path.Combine(_paths.DataPath, DbFilename); using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null)) { @@ -98,6 +98,15 @@ namespace Jellyfin.Server.Migrations.Routines var itemId = new Guid(result[1].ToBlob()); var dtoUserId = new Guid(result[1].ToBlob()); + var client = result[2].ToString(); + var displayPreferencesKey = $"{dtoUserId}|{itemId}|{client}"; + if (displayPrefs.Contains(displayPreferencesKey)) + { + // Duplicate display preference. + continue; + } + + displayPrefs.Add(displayPreferencesKey); var existingUser = _userManager.GetUserById(dtoUserId); if (existingUser == null) { @@ -110,7 +119,7 @@ namespace Jellyfin.Server.Migrations.Routines : ChromecastVersion.Stable; dto.CustomPrefs.Remove("chromecastVersion"); - var displayPreferences = new DisplayPreferences(dtoUserId, itemId, result[2].ToString()) + var displayPreferences = new DisplayPreferences(dtoUserId, itemId, client) { IndexBy = Enum.TryParse(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null, ShowBackdrop = dto.ShowBackdrop, From bf24929d271406bb6ae50eb4aaa784fdf9511b94 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 22 Dec 2020 15:23:55 +0000 Subject: [PATCH 40/45] Changed to 503. --- .../Controllers/DlnaServerController.cs | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index a3c82ed847..df7504b777 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -41,12 +41,12 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Description xml returned. - /// Not found. + /// Service Unavailable. /// An containing the description xml. [HttpGet("{serverId}/description")] [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult GetDescriptionXml([FromRoute, Required] string serverId) @@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers return Ok(xml); } - return NotFound(); + return StatusCode(503); } /// @@ -67,13 +67,13 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna content directory returned. - /// Not found. + /// Service Unavailable. /// An containing the dlna content directory xml. [HttpGet("{serverId}/ContentDirectory")] [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")] [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] @@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers return Ok(_contentDirectory.GetServiceXml()); } - return NotFound(); + return StatusCode(503); } /// @@ -92,13 +92,13 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna media receiver registrar xml returned. - /// Not found. + /// Service Unavailable. /// Dlna media receiver registrar xml. [HttpGet("{serverId}/MediaReceiverRegistrar")] [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")] [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] @@ -109,7 +109,7 @@ namespace Jellyfin.Api.Controllers return Ok(_mediaReceiverRegistrar.GetServiceXml()); } - return NotFound(); + return StatusCode(503); } /// @@ -117,13 +117,13 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna media receiver registrar xml returned. - /// Not found. + /// Service Unavailable. /// Dlna media receiver registrar xml. [HttpGet("{serverId}/ConnectionManager")] [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")] [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] @@ -134,7 +134,7 @@ namespace Jellyfin.Api.Controllers return Ok(_connectionManager.GetServiceXml()); } - return NotFound(); + return StatusCode(503); } /// @@ -142,11 +142,11 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Not found. + /// Service Unavailable. /// Control response. [HttpPost("{serverId}/ContentDirectory/Control")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public async Task> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId) @@ -156,7 +156,7 @@ namespace Jellyfin.Api.Controllers return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false); } - return NotFound(); + return StatusCode(503); } /// @@ -164,11 +164,11 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Not found. + /// Service Unavailable. /// Control response. [HttpPost("{serverId}/ConnectionManager/Control")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public async Task> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId) @@ -178,7 +178,7 @@ namespace Jellyfin.Api.Controllers return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false); } - return NotFound(); + return StatusCode(503); } /// @@ -186,11 +186,11 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Not found. + /// Service Unavailable. /// Control response. [HttpPost("{serverId}/MediaReceiverRegistrar/Control")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public async Task> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId) @@ -200,7 +200,7 @@ namespace Jellyfin.Api.Controllers return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); } - return NotFound(); + return StatusCode(503); } /// @@ -208,14 +208,14 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Not found. + /// Service Unavailable. /// Event subscription response. [HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")] [HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")] [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessMediaReceiverRegistrarEventRequest(string serverId) @@ -225,7 +225,7 @@ namespace Jellyfin.Api.Controllers return ProcessEventRequest(_mediaReceiverRegistrar); } - return NotFound(); + return StatusCode(503); } /// @@ -233,14 +233,14 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Not found. + /// Service Unavailable. /// Event subscription response. [HttpSubscribe("{serverId}/ContentDirectory/Events")] [HttpUnsubscribe("{serverId}/ContentDirectory/Events")] [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessContentDirectoryEventRequest(string serverId) @@ -250,7 +250,7 @@ namespace Jellyfin.Api.Controllers return ProcessEventRequest(_contentDirectory); } - return NotFound(); + return StatusCode(503); } /// @@ -258,14 +258,14 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Not found. + /// Service Unavailable. /// Event subscription response. [HttpSubscribe("{serverId}/ConnectionManager/Events")] [HttpUnsubscribe("{serverId}/ConnectionManager/Events")] [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [Produces(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessConnectionManagerEventRequest(string serverId) @@ -275,7 +275,7 @@ namespace Jellyfin.Api.Controllers return ProcessEventRequest(_connectionManager); } - return NotFound(); + return StatusCode(503); } /// @@ -283,12 +283,15 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// The icon filename. - /// Not found. + /// Request processed. + /// Not Found. + /// Service Unavailable. /// Icon stream. [HttpGet("{serverId}/icons/{fileName}")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [ProducesImageFile] public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName) { @@ -297,7 +300,7 @@ namespace Jellyfin.Api.Controllers return GetIconInternal(fileName); } - return NotFound(); + return StatusCode(503); } /// @@ -305,9 +308,13 @@ namespace Jellyfin.Api.Controllers /// /// The icon filename. /// Icon stream. - /// Not found. + /// Request processed. + /// Not Found. + /// Service Unavailable. [HttpGet("icons/{fileName}")] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [ProducesImageFile] public ActionResult GetIcon([FromRoute, Required] string fileName) { @@ -316,7 +323,7 @@ namespace Jellyfin.Api.Controllers return GetIconInternal(fileName); } - return NotFound(); + return StatusCode(503); } private ActionResult GetIconInternal(string fileName) From bc9462981eb7a3624d82547ea71024442c085ebd Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 22 Dec 2020 15:33:25 +0000 Subject: [PATCH 41/45] changed to constants --- .../Controllers/DlnaServerController.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index df7504b777..694d16ad97 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -41,7 +41,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Description xml returned. - /// Service Unavailable. + /// DLNA is disabled. /// An containing the description xml. [HttpGet("{serverId}/description")] [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")] @@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers return Ok(xml); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -67,7 +67,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna content directory returned. - /// Service Unavailable. + /// DLNA is disabled. /// An containing the dlna content directory xml. [HttpGet("{serverId}/ContentDirectory")] [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")] @@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers return Ok(_contentDirectory.GetServiceXml()); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna media receiver registrar xml returned. - /// Service Unavailable. + /// DLNA is disabled. /// Dlna media receiver registrar xml. [HttpGet("{serverId}/MediaReceiverRegistrar")] [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")] @@ -109,7 +109,7 @@ namespace Jellyfin.Api.Controllers return Ok(_mediaReceiverRegistrar.GetServiceXml()); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Dlna media receiver registrar xml returned. - /// Service Unavailable. + /// DLNA is disabled. /// Dlna media receiver registrar xml. [HttpGet("{serverId}/ConnectionManager")] [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")] @@ -134,7 +134,7 @@ namespace Jellyfin.Api.Controllers return Ok(_connectionManager.GetServiceXml()); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -142,7 +142,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Service Unavailable. + /// DLNA is disabled. /// Control response. [HttpPost("{serverId}/ContentDirectory/Control")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -156,7 +156,7 @@ namespace Jellyfin.Api.Controllers return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -164,7 +164,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Service Unavailable. + /// DLNA is disabled. /// Control response. [HttpPost("{serverId}/ConnectionManager/Control")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -178,7 +178,7 @@ namespace Jellyfin.Api.Controllers return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -186,7 +186,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Service Unavailable. + /// DLNA is disabled. /// Control response. [HttpPost("{serverId}/MediaReceiverRegistrar/Control")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -200,7 +200,7 @@ namespace Jellyfin.Api.Controllers return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -208,7 +208,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Service Unavailable. + /// DLNA is disabled. /// Event subscription response. [HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")] [HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")] @@ -225,7 +225,7 @@ namespace Jellyfin.Api.Controllers return ProcessEventRequest(_mediaReceiverRegistrar); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -233,7 +233,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Service Unavailable. + /// DLNA is disabled. /// Event subscription response. [HttpSubscribe("{serverId}/ContentDirectory/Events")] [HttpUnsubscribe("{serverId}/ContentDirectory/Events")] @@ -250,7 +250,7 @@ namespace Jellyfin.Api.Controllers return ProcessEventRequest(_contentDirectory); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -258,7 +258,7 @@ namespace Jellyfin.Api.Controllers /// /// Server UUID. /// Request processed. - /// Service Unavailable. + /// DLNA is disabled. /// Event subscription response. [HttpSubscribe("{serverId}/ConnectionManager/Events")] [HttpUnsubscribe("{serverId}/ConnectionManager/Events")] @@ -275,7 +275,7 @@ namespace Jellyfin.Api.Controllers return ProcessEventRequest(_connectionManager); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers /// The icon filename. /// Request processed. /// Not Found. - /// Service Unavailable. + /// DLNA is disabled. /// Icon stream. [HttpGet("{serverId}/icons/{fileName}")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] @@ -300,7 +300,7 @@ namespace Jellyfin.Api.Controllers return GetIconInternal(fileName); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } /// @@ -310,7 +310,7 @@ namespace Jellyfin.Api.Controllers /// Icon stream. /// Request processed. /// Not Found. - /// Service Unavailable. + /// DLNA is disabled. [HttpGet("icons/{fileName}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers return GetIconInternal(fileName); } - return StatusCode(503); + return StatusCode(StatusCodes.Status503ServiceUnavailable); } private ActionResult GetIconInternal(string fileName) From 1dac2226c4d12c5ccb9bed83c8b6cceab8dbff09 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 22 Dec 2020 08:57:51 -0700 Subject: [PATCH 42/45] Remove unused deps --- Emby.Dlna/Emby.Dlna.csproj | 4 +--- .../Emby.Server.Implementations.csproj | 8 -------- Jellyfin.Api/Jellyfin.Api.csproj | 2 -- MediaBrowser.Common/MediaBrowser.Common.csproj | 2 +- .../MediaBrowser.MediaEncoding.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 1 - MediaBrowser.Providers/MediaBrowser.Providers.csproj | 1 - 7 files changed, 3 insertions(+), 17 deletions(-) diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index bd30cc1e11..8b057a0950 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -78,9 +78,7 @@ - - - + diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 9e9452f32c..7c9a5fbe19 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -23,14 +23,6 @@ - - - - - - - - diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index b4f2817f71..f01f50cea7 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -15,9 +15,7 @@ - - diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index be5e7f5b45..320e60dc66 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -14,6 +14,7 @@ + @@ -21,7 +22,6 @@ - diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 7bb2a7d03f..f8af499e4c 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -24,7 +24,7 @@ - + diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b86187f9be..334fe82097 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -33,7 +33,6 @@ - diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index accdea36e4..fc8eb8c4e4 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -22,7 +22,6 @@ - From e113a5059794b84090ff3dfb37f52b28ce7651f0 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 22 Dec 2020 16:14:06 +0000 Subject: [PATCH 43/45] Possible null reference fix. --- Jellyfin.Api/Controllers/SystemController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 7784e8a119..d79bea9855 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; +using System.Net; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; @@ -66,7 +67,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetSystemInfo() { - return _appHost.GetSystemInfo(Request.HttpContext.Connection.RemoteIpAddress); + return _appHost.GetSystemInfo(Request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback); } /// @@ -78,7 +79,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetPublicSystemInfo() { - return _appHost.GetPublicSystemInfo(Request.HttpContext.Connection.RemoteIpAddress); + return _appHost.GetPublicSystemInfo(Request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback); } /// From c0c0eaec0586a1dda3d69c0131f00ba1d5966025 Mon Sep 17 00:00:00 2001 From: Ryan Petris Date: Tue, 22 Dec 2020 20:37:07 -0700 Subject: [PATCH 44/45] new List(int) does not pre-allocate indicies like Arrays, it merely sets the initial capacity. --- .../LiveTv/Listings/SchedulesDirect.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 90e6cc9668..b19ccadd8f 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -784,18 +784,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings var allStations = root.stations ?? new List(); var map = root.map; - int len = map.Count; - var array = new List(len); - for (int i = 0; i < len; i++) + var list = new List(map.Count); + foreach (var channel in map) { - var channelNumber = GetChannelNumber(map[i]); + var channelNumber = GetChannelNumber(channel); - var station = allStations.Find(item => string.Equals(item.stationID, map[i].stationID, StringComparison.OrdinalIgnoreCase)); + var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)); if (station == null) { station = new ScheduleDirect.Station { - stationID = map[i].stationID + stationID = channel.stationID }; } @@ -812,10 +811,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings channelInfo.ImageUrl = station.logo.URL; } - array[i] = channelInfo; + list.Add(channelInfo); } - return array; + return list; } private static string NormalizeName(string value) From af8acf7128c66b369cd8cad0aaebc4c3dd963c41 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 23 Dec 2020 09:01:24 -0700 Subject: [PATCH 45/45] Initialize JsonSerializerOptions statically --- MediaBrowser.Common/Json/JsonDefaults.cs | 78 ++++++++++++++---------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 128cc9d5d0..1ec2a87c53 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -19,56 +19,70 @@ namespace MediaBrowser.Common.Json /// public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\""; + /// + /// When changing these options, update + /// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs + /// -> AddJellyfinApi + /// -> AddJsonOptions. + /// + private static readonly JsonSerializerOptions _jsonSerializerOptions = new () + { + ReadCommentHandling = JsonCommentHandling.Disallow, + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + Converters = + { + new JsonGuidConverter(), + new JsonVersionConverter(), + new JsonStringEnumConverter(), + new JsonNullableStructConverterFactory(), + new JsonBoolNumberConverter(), + new JsonDateTimeConverter() + } + }; + + private static readonly JsonSerializerOptions _pascalCaseJsonSerializerOptions = new (_jsonSerializerOptions) + { + PropertyNamingPolicy = null + }; + + private static readonly JsonSerializerOptions _camelCaseJsonSerializerOptions = new (_jsonSerializerOptions) + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + /// /// Gets the default options. /// /// - /// When changing these options, update - /// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs - /// -> AddJellyfinApi - /// -> AddJsonOptions. + /// The return value must not be modified. + /// If the defaults must be modified the author must use the copy constructor. /// /// The default options. public static JsonSerializerOptions GetOptions() - { - var options = new JsonSerializerOptions - { - ReadCommentHandling = JsonCommentHandling.Disallow, - WriteIndented = false, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - NumberHandling = JsonNumberHandling.AllowReadingFromString - }; - - options.Converters.Add(new JsonGuidConverter()); - options.Converters.Add(new JsonVersionConverter()); - options.Converters.Add(new JsonStringEnumConverter()); - options.Converters.Add(new JsonNullableStructConverterFactory()); - options.Converters.Add(new JsonBoolNumberConverter()); - options.Converters.Add(new JsonDateTimeConverter()); - - return options; - } + => _jsonSerializerOptions; /// /// Gets camelCase json options. /// + /// + /// The return value must not be modified. + /// If the defaults must be modified the author must use the copy constructor. + /// /// The camelCase options. public static JsonSerializerOptions GetCamelCaseOptions() - { - var options = GetOptions(); - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - return options; - } + => _camelCaseJsonSerializerOptions; /// /// Gets PascalCase json options. /// + /// + /// The return value must not be modified. + /// If the defaults must be modified the author must use the copy constructor. + /// /// The PascalCase options. public static JsonSerializerOptions GetPascalCaseOptions() - { - var options = GetOptions(); - options.PropertyNamingPolicy = null; - return options; - } + => _pascalCaseJsonSerializerOptions; } }