From 2363ad544979adf32207fa927f106fadb784f1fb Mon Sep 17 00:00:00 2001 From: redSpoutnik <15638041+redSpoutnik@users.noreply.github.com> Date: Fri, 8 May 2020 21:53:38 +0200 Subject: [PATCH 001/203] Add Post subtitle in API --- MediaBrowser.Api/Subtitles/SubtitleService.cs | 55 +++++++++++++++ .../Subtitles/ISubtitleManager.cs | 6 ++ .../Subtitles/SubtitleManager.cs | 70 ++++++++++++------- 3 files changed, 104 insertions(+), 27 deletions(-) diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index f2968c6b5c..ed2533f6d4 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -37,6 +37,31 @@ namespace MediaBrowser.Api.Subtitles public int Index { get; set; } } + [Route("/Videos/{Id}/Subtitles", "POST", Summary = "Upload an external subtitle file")] + [Authenticated(Roles = "admin")] + public class PostSubtitle : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Language { get; set; } + + [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Format { get; set; } + + [ApiMember(Name = "IsForced", Description = "IsForced", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string IsForced { get; set; } + + [ApiMember(Name = "Data", Description = "Data", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Data { get; set; } + + } + [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")] [Authenticated] public class SearchRemoteSubtitles : IReturn @@ -270,6 +295,36 @@ namespace MediaBrowser.Api.Subtitles return _subtitleManager.DeleteSubtitles(item, request.Index); } + public void Post(PostSubtitle request) + { + var video = (Video)_libraryManager.GetItemById(request.Id); + + var bytes = Convert.FromBase64String(request.Data); + var memoryStream = new MemoryStream(bytes) + { + Position = 0 + }; + + Task.Run(async () => + { + try + { + await _subtitleManager.UploadSubtitle(video, new SubtitleResponse { + Format = request.Format, + Language = request.Language, + IsForced = Convert.ToBoolean(request.IsForced), + Stream = memoryStream + }).ConfigureAwait(false); + + _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error uploading subtitle"); + } + }); + } + public async Task Get(GetRemoteSubtitles request) { var result = await _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 39538aacd7..556e2f66c8 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; @@ -50,6 +51,11 @@ namespace MediaBrowser.Controller.Subtitles /// Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken); + /// + /// Upload new subtitle. + /// + Task UploadSubtitle(Video video, SubtitleResponse response); + /// /// Gets the remote subtitles. /// diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 127d29c04e..2b55618a8e 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -147,37 +147,11 @@ namespace MediaBrowser.Providers.Subtitles var parts = subtitleId.Split(new[] { '_' }, 2); var provider = GetProvider(parts.First()); - var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia; - try { var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false); - using (var stream = response.Stream) - using (var memoryStream = new MemoryStream()) - { - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - - var savePaths = new List(); - var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); - - if (response.IsForced) - { - saveFileName += ".forced"; - } - - saveFileName += "." + response.Format.ToLowerInvariant(); - - if (saveInMediaFolder) - { - savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName)); - } - - savePaths.Add(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); - - await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); - } + await TrySaveSubtitle(video, libraryOptions, response); } catch (RateLimitExceededException) { @@ -196,6 +170,48 @@ namespace MediaBrowser.Providers.Subtitles } } + /// + public async Task UploadSubtitle(Video video, SubtitleResponse response) + { + var libraryOptions = BaseItem.LibraryManager.GetLibraryOptions(video); + + await TrySaveSubtitle(video, libraryOptions, response); + } + + private async Task TrySaveSubtitle( + Video video, + LibraryOptions libraryOptions, + SubtitleResponse response) + { + var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia; + + using (var stream = response.Stream) + using (var memoryStream = new MemoryStream()) + { + await stream.CopyToAsync(memoryStream).ConfigureAwait(false); + memoryStream.Position = 0; + + var savePaths = new List(); + var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); + + if (response.IsForced) + { + saveFileName += ".forced"; + } + + saveFileName += "." + response.Format.ToLowerInvariant(); + + if (saveInMediaFolder) + { + savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName)); + } + + savePaths.Add(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); + + await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); + } + } + private async Task TrySaveToFiles(Stream stream, List savePaths) { Exception exceptionToThrow = null; From ba03ed65fe64b724b3e8b5b94b9cbe1075c61da2 Mon Sep 17 00:00:00 2001 From: ferferga Date: Wed, 27 May 2020 19:13:41 +0200 Subject: [PATCH 002/203] Remove "download images in advance" option --- .../Configuration/LibraryOptions.cs | 1 - .../Manager/ItemImageProvider.cs | 12 +---------- .../Manager/MetadataService.cs | 21 ++++++++----------- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 4342ccd8ae..01d6665621 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -12,7 +12,6 @@ namespace MediaBrowser.Model.Configuration public bool EnableRealtimeMonitor { get; set; } public bool EnableChapterImageExtraction { get; set; } public bool ExtractChapterImagesDuringLibraryScan { get; set; } - public bool DownloadImagesInAdvance { get; set; } public MediaPathInfo[] PathInfos { get; set; } public bool SaveLocalMetadata { get; set; } diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 6ef0e44a2b..60270d80cb 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -495,17 +495,7 @@ namespace MediaBrowser.Providers.Manager } } - if (libraryOptions.DownloadImagesInAdvance) - { - return false; - } - - //if (!item.IsSaveLocalMetadataEnabled()) - //{ - // return true; - //} - - return true; + return false; } private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable urls) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index c49aa407aa..dffdc468ad 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -252,18 +252,15 @@ namespace MediaBrowser.Providers.Manager private void AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken) { - //if (libraryOptions.DownloadImagesInAdvance) - //{ - // try - // { - // await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - // return; - // } - // catch (Exception ex) - // { - // Logger.LogError(ex, "Error in AddPersonImage"); - // } - //} + try + { + await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); + return; + } + catch (Exception ex) + { + Logger.LogError(ex, "Error in AddPersonImage"); + } personEntity.SetImage(new ItemImageInfo { From c888b34a6219be0c06331e568d66a8fdf17bceaa Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Fri, 24 Jul 2020 21:19:56 +0800 Subject: [PATCH 003/203] add a method to use custom fallback fonts for subtitle rendering --- MediaBrowser.Api/Subtitles/SubtitleService.cs | 53 +++++++++++++++++++ .../Configuration/EncodingOptions.cs | 5 ++ 2 files changed, 58 insertions(+) diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index a70da8e56c..198e459482 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -122,8 +123,15 @@ namespace MediaBrowser.Api.Subtitles public int SegmentLength { get; set; } } + [Route("/FallbackFont/Font", "GET", Summary = "Gets the fallback font file")] + [Authenticated] + public class GetFallbackFont + { + } + public class SubtitleService : BaseApiService { + private readonly IServerConfigurationManager _serverConfigurationManager; private readonly ILibraryManager _libraryManager; private readonly ISubtitleManager _subtitleManager; private readonly ISubtitleEncoder _subtitleEncoder; @@ -145,6 +153,7 @@ namespace MediaBrowser.Api.Subtitles IAuthorizationContext authContext) : base(logger, serverConfigurationManager, httpResultFactory) { + _serverConfigurationManager = serverConfigurationManager; _libraryManager = libraryManager; _subtitleManager = subtitleManager; _subtitleEncoder = subtitleEncoder; @@ -298,5 +307,49 @@ namespace MediaBrowser.Api.Subtitles } }); } + + public async Task Get(GetFallbackFont request) + { + var fallbackFontPath = EncodingConfigurationExtensions.GetEncodingOptions(_serverConfigurationManager).FallbackFontPath; + + if (!string.IsNullOrEmpty(fallbackFontPath)) + { + var directoryService = new DirectoryService(_fileSystem); + + try + { + // 10 Megabytes + var maxSize = 10485760; + var fontFile = directoryService.GetFile(fallbackFontPath); + var fileSize = fontFile?.Length; + + if (fileSize != null && fileSize > 0) + { + Logger.LogInformation("Fallback font size is {0} Bytes", fileSize); + + if (fileSize <= maxSize) + { + return await ResultFactory.GetStaticFileResult(Request, fontFile.FullName); + } + + Logger.LogInformation("The selected font is too large. Maximum allowed size is 10 Megabytes"); + } + else + { + Logger.LogInformation("The selected font is null or empty"); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error reading fallback font file"); + } + } + else + { + Logger.LogInformation("The path of fallback font has not been set"); + } + + return string.Empty; + } } } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 9a30f7e9f0..aa6ed5bb9f 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -9,6 +9,10 @@ namespace MediaBrowser.Model.Configuration public string TranscodingTempPath { get; set; } + public string FallbackFontPath { get; set; } + + public bool EnableFallbackFont { get; set; } + public double DownMixAudioBoost { get; set; } public bool EnableThrottling { get; set; } @@ -49,6 +53,7 @@ namespace MediaBrowser.Model.Configuration public EncodingOptions() { + EnableFallbackFont = false; DownMixAudioBoost = 2; EnableThrottling = false; ThrottleDelaySeconds = 180; From a78c8e38544a974f07c6efce1af4e7fbef83b2be Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sat, 25 Jul 2020 01:50:18 +0800 Subject: [PATCH 004/203] change loglevels --- MediaBrowser.Api/Subtitles/SubtitleService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index 198e459482..b06ffd1851 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -325,18 +325,18 @@ namespace MediaBrowser.Api.Subtitles if (fileSize != null && fileSize > 0) { - Logger.LogInformation("Fallback font size is {0} Bytes", fileSize); + Logger.LogDebug("Fallback font size is {0} Bytes", fileSize); if (fileSize <= maxSize) { return await ResultFactory.GetStaticFileResult(Request, fontFile.FullName); } - Logger.LogInformation("The selected font is too large. Maximum allowed size is 10 Megabytes"); + Logger.LogWarning("The selected font is too large. Maximum allowed size is 10 Megabytes"); } else { - Logger.LogInformation("The selected font is null or empty"); + Logger.LogWarning("The selected font is null or empty"); } } catch (Exception ex) @@ -346,7 +346,7 @@ namespace MediaBrowser.Api.Subtitles } else { - Logger.LogInformation("The path of fallback font has not been set"); + Logger.LogWarning("The path of fallback font has not been set"); } return string.Empty; From 1883ecd81716acb108424924711aed842dd6338a Mon Sep 17 00:00:00 2001 From: MichaIng Date: Fri, 24 Jul 2020 22:43:32 +0200 Subject: [PATCH 005/203] Fix left /usr/bin/jellyfin symlink on removal and typo After removal of the symlink target file "/usr/lib/jellyfin/bin/jellyfin", file existence check on the symlink "[[ -f /usr/bin/jellyfin ]]" returns false. As a result the symlink is left in place on package purge. The correct check would be "[[ -L /usr/bin/jellyfin ]]", but since it could be a file in cases, e.g. manual fix on file systems with no symlink support or for any other reason, it is easiest to use "rm -f" to assure that it is removed in both cases and not return false even if it does not exist at all. Additionally this fixes a typo on upstart script check. Signed-off-by: MichaIng --- debian/postrm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/postrm b/debian/postrm index 1d00a984ec..3d56a5f1e8 100644 --- a/debian/postrm +++ b/debian/postrm @@ -25,7 +25,7 @@ case "$1" in purge) echo PURGE | debconf-communicate $NAME > /dev/null 2>&1 || true - if [[ -x "/etc/init.d/jellyfin" ]] || [[ -e "/etc/init/jellyfin.connf" ]]; then + if [[ -x "/etc/init.d/jellyfin" ]] || [[ -e "/etc/init/jellyfin.conf" ]]; then update-rc.d jellyfin remove >/dev/null 2>&1 || true fi @@ -54,7 +54,7 @@ case "$1" in rm -rf $PROGRAMDATA fi # Remove binary symlink - [[ -f /usr/bin/jellyfin ]] && rm /usr/bin/jellyfin + rm -f /usr/bin/jellyfin # Remove sudoers config [[ -f /etc/sudoers.d/jellyfin-sudoers ]] && rm /etc/sudoers.d/jellyfin-sudoers # Remove anything at the default locations; catches situations where the user moved the defaults From 45a97056ea9fe7bc247f205931f5b0e0f2a1ab75 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 29 Jul 2020 02:44:11 +0800 Subject: [PATCH 006/203] allows to provide multiple fonts --- MediaBrowser.Api/Subtitles/SubtitleService.cs | 81 +++++++++++++++++-- MediaBrowser.Model/Subtitles/FontFile.cs | 34 ++++++++ 2 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 MediaBrowser.Model/Subtitles/FontFile.cs diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index b06ffd1851..b5f5213fe3 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Subtitles; using Microsoft.Extensions.Logging; using MimeTypes = MediaBrowser.Model.Net.MimeTypes; @@ -123,10 +124,18 @@ namespace MediaBrowser.Api.Subtitles public int SegmentLength { get; set; } } + [Route("/FallbackFont/FontList", "GET", Summary = "Gets the fallback font list")] + [Authenticated] + public class GetFallbackFontList + { + } + [Route("/FallbackFont/Font", "GET", Summary = "Gets the fallback font file")] [Authenticated] public class GetFallbackFont { + [ApiMember(Name = "Name", Description = "The font file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Name { get; set; } } public class SubtitleService : BaseApiService @@ -308,19 +317,74 @@ namespace MediaBrowser.Api.Subtitles }); } - public async Task Get(GetFallbackFont request) + public object Get(GetFallbackFontList request) { - var fallbackFontPath = EncodingConfigurationExtensions.GetEncodingOptions(_serverConfigurationManager).FallbackFontPath; + IEnumerable fontFiles = Enumerable.Empty(); + + var encodingOptions = EncodingConfigurationExtensions.GetEncodingOptions(_serverConfigurationManager); + var fallbackFontPath = encodingOptions.FallbackFontPath; if (!string.IsNullOrEmpty(fallbackFontPath)) { - var directoryService = new DirectoryService(_fileSystem); + try + { + fontFiles = _fileSystem.GetFiles(fallbackFontPath, new[] { ".woff", ".woff2", ".ttf", ".otf" }, false, false); + + var result = fontFiles.Select(i => new FontFile + { + Name = i.Name, + Size = i.Length, + DateCreated = _fileSystem.GetCreationTimeUtc(i), + DateModified = _fileSystem.GetLastWriteTimeUtc(i) + }).OrderBy(i => i.Size) + .ThenBy(i => i.Name) + .ThenByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated) + .ToArray(); + + // max total size 20M + var maxSize = 20971520; + var sizeCounter = 0L; + for (int i = 0; i < result.Length; i++) + { + sizeCounter += result[i].Size; + if (sizeCounter >= maxSize) + { + Logger.LogWarning("Some fonts will not be sent due to size limitations"); + Array.Resize(ref result, i); + break; + } + } + + return ToOptimizedResult(result); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error getting fallback font list"); + } + } + else + { + Logger.LogWarning("The path of fallback font folder has not been set"); + encodingOptions.EnableFallbackFont = false; + } + return ResultFactory.GetResult(Request, "[]", "application/json"); + } + + public async Task Get(GetFallbackFont request) + { + var encodingOptions = EncodingConfigurationExtensions.GetEncodingOptions(_serverConfigurationManager); + var fallbackFontPath = encodingOptions.FallbackFontPath; + + if (!string.IsNullOrEmpty(fallbackFontPath)) + { try { - // 10 Megabytes + // max single font size 10M var maxSize = 10485760; - var fontFile = directoryService.GetFile(fallbackFontPath); + var fontFile = _fileSystem.GetFiles(fallbackFontPath) + .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase)); var fileSize = fontFile?.Length; if (fileSize != null && fileSize > 0) @@ -341,15 +405,16 @@ namespace MediaBrowser.Api.Subtitles } catch (Exception ex) { - Logger.LogError(ex, "Error reading fallback font file"); + Logger.LogError(ex, "Error reading fallback font"); } } else { - Logger.LogWarning("The path of fallback font has not been set"); + Logger.LogWarning("The path of fallback font folder has not been set"); + encodingOptions.EnableFallbackFont = false; } - return string.Empty; + return ResultFactory.GetResult(Request, string.Empty, "text/plain"); } } } diff --git a/MediaBrowser.Model/Subtitles/FontFile.cs b/MediaBrowser.Model/Subtitles/FontFile.cs new file mode 100644 index 0000000000..13963a9cb5 --- /dev/null +++ b/MediaBrowser.Model/Subtitles/FontFile.cs @@ -0,0 +1,34 @@ +#nullable disable +#pragma warning disable CS1591 + +using System; + +namespace MediaBrowser.Model.Subtitles +{ + public class FontFile + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the size. + /// + /// The size. + public long Size { get; set; } + + /// + /// Gets or sets the date created. + /// + /// The date created. + public DateTime DateCreated { get; set; } + + /// + /// Gets or sets the date modified. + /// + /// The date modified. + public DateTime DateModified { get; set; } + } +} From 893bc7400bc2dadf2a789ba439d9a469c13b05df Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 30 Jul 2020 16:30:49 +0800 Subject: [PATCH 007/203] update as per suggestions --- MediaBrowser.Api/Subtitles/SubtitleService.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index b5f5213fe3..e275d3a285 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Mime; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -124,13 +125,13 @@ namespace MediaBrowser.Api.Subtitles public int SegmentLength { get; set; } } - [Route("/FallbackFont/FontList", "GET", Summary = "Gets the fallback font list")] + [Route("/FallbackFont/Fonts", "GET", Summary = "Gets the fallback font list")] [Authenticated] public class GetFallbackFontList { } - [Route("/FallbackFont/Font", "GET", Summary = "Gets the fallback font file")] + [Route("/FallbackFont/Fonts/{Name}", "GET", Summary = "Gets the fallback font file")] [Authenticated] public class GetFallbackFont { @@ -369,7 +370,7 @@ namespace MediaBrowser.Api.Subtitles encodingOptions.EnableFallbackFont = false; } - return ResultFactory.GetResult(Request, "[]", "application/json"); + return ResultFactory.GetResult(Request, "[]", MediaTypeNames.Application.Json); } public async Task Get(GetFallbackFont request) @@ -414,7 +415,7 @@ namespace MediaBrowser.Api.Subtitles encodingOptions.EnableFallbackFont = false; } - return ResultFactory.GetResult(Request, string.Empty, "text/plain"); + return ResultFactory.GetResult(Request, string.Empty, MediaTypeNames.Text.Plain); } } } From 8165c3bb21a1ac4869dafa951f8a3ca3d21dda1e Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 30 Jul 2020 17:36:38 +0800 Subject: [PATCH 008/203] total font size limit 20M --- MediaBrowser.Api/Subtitles/SubtitleService.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index e275d3a285..bd4da04f90 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -382,8 +382,6 @@ namespace MediaBrowser.Api.Subtitles { try { - // max single font size 10M - var maxSize = 10485760; var fontFile = _fileSystem.GetFiles(fallbackFontPath) .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase)); var fileSize = fontFile?.Length; @@ -391,13 +389,7 @@ namespace MediaBrowser.Api.Subtitles if (fileSize != null && fileSize > 0) { Logger.LogDebug("Fallback font size is {0} Bytes", fileSize); - - if (fileSize <= maxSize) - { - return await ResultFactory.GetStaticFileResult(Request, fontFile.FullName); - } - - Logger.LogWarning("The selected font is too large. Maximum allowed size is 10 Megabytes"); + return await ResultFactory.GetStaticFileResult(Request, fontFile.FullName); } else { From 988e76d4c7df48370a6cb1de59c4909d140f96e2 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 19 Aug 2020 17:30:22 +0800 Subject: [PATCH 009/203] update endpoints as AspNet controllers --- .../Controllers/SubtitleController.cs | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 988acccc3a..387c9b0953 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -10,6 +10,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; @@ -20,6 +22,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Subtitles; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -33,6 +36,7 @@ namespace Jellyfin.Api.Controllers [Route("")] public class SubtitleController : BaseJellyfinApiController { + private readonly IServerConfigurationManager _serverConfigurationManager; private readonly ILibraryManager _libraryManager; private readonly ISubtitleManager _subtitleManager; private readonly ISubtitleEncoder _subtitleEncoder; @@ -45,6 +49,7 @@ namespace Jellyfin.Api.Controllers /// /// Initializes a new instance of the class. /// + /// Instance of interface. /// Instance of interface. /// Instance of interface. /// Instance of interface. @@ -54,6 +59,7 @@ namespace Jellyfin.Api.Controllers /// Instance of interface. /// Instance of interface. public SubtitleController( + IServerConfigurationManager serverConfigurationManager, ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder, @@ -63,6 +69,7 @@ namespace Jellyfin.Api.Controllers IAuthorizationContext authContext, ILogger logger) { + _serverConfigurationManager = serverConfigurationManager; _libraryManager = libraryManager; _subtitleManager = subtitleManager; _subtitleEncoder = subtitleEncoder; @@ -346,5 +353,116 @@ namespace Jellyfin.Api.Controllers copyTimestamps, CancellationToken.None); } + + /// + /// Gets a list of available fallback font files. + /// + /// Information retrieved. + /// An array of with the available font files. + [HttpGet("/FallbackFont/Fonts")] + [Authorize(Policy = Policies.DefaultAuthorization)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetFallbackFontList() + { + IEnumerable fontFiles = Enumerable.Empty(); + + var encodingOptions = EncodingConfigurationExtensions.GetEncodingOptions(_serverConfigurationManager); + var fallbackFontPath = encodingOptions.FallbackFontPath; + + if (!string.IsNullOrEmpty(fallbackFontPath)) + { + try + { + fontFiles = _fileSystem.GetFiles(fallbackFontPath, new[] { ".woff", ".woff2", ".ttf", ".otf" }, false, false); + + var result = fontFiles.Select(i => new FontFile + { + Name = i.Name, + Size = i.Length, + DateCreated = _fileSystem.GetCreationTimeUtc(i), + DateModified = _fileSystem.GetLastWriteTimeUtc(i) + }).OrderBy(i => i.Size) + .ThenBy(i => i.Name) + .ThenByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated) + .ToArray(); + + // max total size 20M + var maxSize = 20971520; + var sizeCounter = 0L; + for (int i = 0; i < result.Length; i++) + { + sizeCounter += result[i].Size; + if (sizeCounter >= maxSize) + { + _logger.LogWarning("Some fonts will not be sent due to size limitations"); + Array.Resize(ref result, i); + break; + } + } + + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting fallback font list"); + } + } + else + { + _logger.LogWarning("The path of fallback font folder has not been set"); + encodingOptions.EnableFallbackFont = false; + } + + return File(Encoding.UTF8.GetBytes("[]"), MediaTypeNames.Application.Json); + } + + /// + /// Gets a fallback font file. + /// + /// The name of the fallback font file to get. + /// Fallback font file retrieved. + /// The fallback font file. + [HttpGet("/FallbackFont/Fonts/{name}")] + [Authorize(Policy = Policies.DefaultAuthorization)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetFallbackFont([FromRoute, Required] string? name) + { + var encodingOptions = EncodingConfigurationExtensions.GetEncodingOptions(_serverConfigurationManager); + var fallbackFontPath = encodingOptions.FallbackFontPath; + + if (!string.IsNullOrEmpty(fallbackFontPath)) + { + try + { + var fontFile = _fileSystem.GetFiles(fallbackFontPath) + .First(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); + var fileSize = fontFile?.Length; + + if (fontFile != null && fileSize != null && fileSize > 0) + { + _logger.LogDebug("Fallback font size is {0} Bytes", fileSize); + + FileStream stream = new FileStream(fontFile.FullName, FileMode.Open, FileAccess.Read); + return File(stream, MimeTypes.GetMimeType(fontFile.FullName)); + } + else + { + _logger.LogWarning("The selected font is null or empty"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading fallback font"); + } + } + else + { + _logger.LogWarning("The path of fallback font folder has not been set"); + encodingOptions.EnableFallbackFont = false; + } + + return File(Encoding.UTF8.GetBytes(string.Empty), MediaTypeNames.Text.Plain); + } } } From 53861db45139022907c24a43bea43225f34c6a6d Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 20 Aug 2020 00:04:55 +0800 Subject: [PATCH 010/203] Apply suggestions from code review Co-authored-by: Cody Robibero --- .../Controllers/SubtitleController.cs | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 387c9b0953..0795f8f649 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -359,53 +359,52 @@ namespace Jellyfin.Api.Controllers /// /// Information retrieved. /// An array of with the available font files. - [HttpGet("/FallbackFont/Fonts")] + [HttpGet("FallbackFont/Fonts")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetFallbackFontList() + /// + /// Gets a list of available fallback font files. + /// + /// Information retrieved. + /// An array of with the available font files. + [HttpGet("FallbackFont/Fonts")] + [Authorize(Policy = Policies.DefaultAuthorization)] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable GetFallbackFontList() { - IEnumerable fontFiles = Enumerable.Empty(); - - var encodingOptions = EncodingConfigurationExtensions.GetEncodingOptions(_serverConfigurationManager); + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); var fallbackFontPath = encodingOptions.FallbackFontPath; if (!string.IsNullOrEmpty(fallbackFontPath)) { - try - { - fontFiles = _fileSystem.GetFiles(fallbackFontPath, new[] { ".woff", ".woff2", ".ttf", ".otf" }, false, false); + var files = _fileSystem.GetFiles(fallbackFontPath, new[] { ".woff", ".woff2", ".ttf", ".otf" }, false, false); - var result = fontFiles.Select(i => new FontFile + var fontFiles = files + .Select(i => new FontFile { Name = i.Name, Size = i.Length, DateCreated = _fileSystem.GetCreationTimeUtc(i), DateModified = _fileSystem.GetLastWriteTimeUtc(i) - }).OrderBy(i => i.Size) - .ThenBy(i => i.Name) - .ThenByDescending(i => i.DateModified) - .ThenByDescending(i => i.DateCreated) - .ToArray(); - - // max total size 20M - var maxSize = 20971520; - var sizeCounter = 0L; - for (int i = 0; i < result.Length; i++) + }) + .OrderBy(i => i.Size) + .ThenBy(i => i.Name) + .ThenByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated); + + // max total size 20M + const int MaxSize = 20971520; + var sizeCounter = 0L; + foreach (var fontFile in fontFiles) + { + sizeCounter += fontFile.Size; + if (sizeCounter >= MaxSize) { - sizeCounter += result[i].Size; - if (sizeCounter >= maxSize) - { - _logger.LogWarning("Some fonts will not be sent due to size limitations"); - Array.Resize(ref result, i); - break; - } + _logger.LogWarning("Some fonts will not be sent due to size limitations"); + yield break; } - return result; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting fallback font list"); + yield return fontFile; } } else @@ -413,8 +412,6 @@ namespace Jellyfin.Api.Controllers _logger.LogWarning("The path of fallback font folder has not been set"); encodingOptions.EnableFallbackFont = false; } - - return File(Encoding.UTF8.GetBytes("[]"), MediaTypeNames.Application.Json); } /// @@ -423,12 +420,12 @@ namespace Jellyfin.Api.Controllers /// The name of the fallback font file to get. /// Fallback font file retrieved. /// The fallback font file. - [HttpGet("/FallbackFont/Fonts/{name}")] + [HttpGet("FallbackFont/Fonts/{name}")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetFallbackFont([FromRoute, Required] string? name) + public ActionResult GetFallbackFont([FromRoute, Required] string name) { - var encodingOptions = EncodingConfigurationExtensions.GetEncodingOptions(_serverConfigurationManager); + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); var fallbackFontPath = encodingOptions.FallbackFontPath; if (!string.IsNullOrEmpty(fallbackFontPath)) @@ -441,7 +438,7 @@ namespace Jellyfin.Api.Controllers if (fontFile != null && fileSize != null && fileSize > 0) { - _logger.LogDebug("Fallback font size is {0} Bytes", fileSize); + _logger.LogDebug("Fallback font size is {fileSize} Bytes", fileSize); FileStream stream = new FileStream(fontFile.FullName, FileMode.Open, FileAccess.Read); return File(stream, MimeTypes.GetMimeType(fontFile.FullName)); From 16658c92fe1baeb23909001525008208cfc82f75 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 20 Aug 2020 00:08:44 +0800 Subject: [PATCH 011/203] fix SubtitlesOctopus --- .../Controllers/SubtitleController.cs | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 0795f8f649..6c9d1beecd 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -354,14 +354,6 @@ namespace Jellyfin.Api.Controllers CancellationToken.None); } - /// - /// Gets a list of available fallback font files. - /// - /// Information retrieved. - /// An array of with the available font files. - [HttpGet("FallbackFont/Fonts")] - [Authorize(Policy = Policies.DefaultAuthorization)] - [ProducesResponseType(StatusCodes.Status200OK)] /// /// Gets a list of available fallback font files. /// @@ -378,7 +370,6 @@ namespace Jellyfin.Api.Controllers if (!string.IsNullOrEmpty(fallbackFontPath)) { var files = _fileSystem.GetFiles(fallbackFontPath, new[] { ".woff", ".woff2", ".ttf", ".otf" }, false, false); - var fontFiles = files .Select(i => new FontFile { @@ -391,7 +382,6 @@ namespace Jellyfin.Api.Controllers .ThenBy(i => i.Name) .ThenByDescending(i => i.DateModified) .ThenByDescending(i => i.DateCreated); - // max total size 20M const int MaxSize = 20971520; var sizeCounter = 0L; @@ -403,7 +393,6 @@ namespace Jellyfin.Api.Controllers _logger.LogWarning("Some fonts will not be sent due to size limitations"); yield break; } - yield return fontFile; } } @@ -430,27 +419,20 @@ namespace Jellyfin.Api.Controllers if (!string.IsNullOrEmpty(fallbackFontPath)) { - try - { - var fontFile = _fileSystem.GetFiles(fallbackFontPath) - .First(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); - var fileSize = fontFile?.Length; + var fontFile = _fileSystem.GetFiles(fallbackFontPath) + .First(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); + var fileSize = fontFile?.Length; - if (fontFile != null && fileSize != null && fileSize > 0) - { - _logger.LogDebug("Fallback font size is {fileSize} Bytes", fileSize); + if (fontFile != null && fileSize != null && fileSize > 0) + { + _logger.LogDebug("Fallback font size is {fileSize} Bytes", fileSize); - FileStream stream = new FileStream(fontFile.FullName, FileMode.Open, FileAccess.Read); - return File(stream, MimeTypes.GetMimeType(fontFile.FullName)); - } - else - { - _logger.LogWarning("The selected font is null or empty"); - } + FileStream stream = new FileStream(fontFile.FullName, FileMode.Open, FileAccess.Read); + return File(stream, MimeTypes.GetMimeType(fontFile.FullName)); } - catch (Exception ex) + else { - _logger.LogError(ex, "Error reading fallback font"); + _logger.LogWarning("The selected font is null or empty"); } } else @@ -459,7 +441,8 @@ namespace Jellyfin.Api.Controllers encodingOptions.EnableFallbackFont = false; } - return File(Encoding.UTF8.GetBytes(string.Empty), MediaTypeNames.Text.Plain); + // returning HTTP 204 will break the SubtitlesOctopus + return Ok(); } } } From dd3b48f2039fe299a55db437190a9e0d5c78145c Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 20 Aug 2020 15:17:42 +0800 Subject: [PATCH 012/203] fix lint --- Jellyfin.Api/Controllers/SubtitleController.cs | 3 ++- MediaBrowser.Model/Subtitles/FontFile.cs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 6c9d1beecd..912de0fd60 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -393,6 +393,7 @@ namespace Jellyfin.Api.Controllers _logger.LogWarning("Some fonts will not be sent due to size limitations"); yield break; } + yield return fontFile; } } @@ -425,7 +426,7 @@ namespace Jellyfin.Api.Controllers if (fontFile != null && fileSize != null && fileSize > 0) { - _logger.LogDebug("Fallback font size is {fileSize} Bytes", fileSize); + _logger.LogDebug("Fallback font size is {FileSize} Bytes", fileSize); FileStream stream = new FileStream(fontFile.FullName, FileMode.Open, FileAccess.Read); return File(stream, MimeTypes.GetMimeType(fontFile.FullName)); diff --git a/MediaBrowser.Model/Subtitles/FontFile.cs b/MediaBrowser.Model/Subtitles/FontFile.cs index 13963a9cb5..115c492957 100644 --- a/MediaBrowser.Model/Subtitles/FontFile.cs +++ b/MediaBrowser.Model/Subtitles/FontFile.cs @@ -1,17 +1,17 @@ -#nullable disable -#pragma warning disable CS1591 - using System; namespace MediaBrowser.Model.Subtitles { + /// + /// Class FontFile. + /// public class FontFile { /// /// Gets or sets the name. /// /// The name. - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the size. From 05e78ee78c56364971956507f6239ded61f0af87 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 20 Aug 2020 22:53:36 +0800 Subject: [PATCH 013/203] Apply suggestions from code review Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/SubtitleController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 912de0fd60..8550a86e0c 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -427,9 +427,7 @@ namespace Jellyfin.Api.Controllers if (fontFile != null && fileSize != null && fileSize > 0) { _logger.LogDebug("Fallback font size is {FileSize} Bytes", fileSize); - - FileStream stream = new FileStream(fontFile.FullName, FileMode.Open, FileAccess.Read); - return File(stream, MimeTypes.GetMimeType(fontFile.FullName)); + return PhysicalFile(fontFile.FullName, MimeTypes.GetMimeType(fontFile.FullName)); } else { From a3020f2917176baef19653a485cc0aaaeb8d00f2 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 21 Aug 2020 19:53:55 +0200 Subject: [PATCH 014/203] Use backdrop with library name as library thumbnail --- Emby.Drawing/ImageProcessor.cs | 6 +- Emby.Drawing/NullImageEncoder.cs | 2 +- .../Images/BaseDynamicImageProvider.cs | 13 ++++- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 4 +- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 58 ++++++++++--------- .../Drawing/IImageEncoder.cs | 3 +- .../Drawing/IImageProcessor.cs | 3 +- 7 files changed, 54 insertions(+), 35 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index f585b90ca1..e86c22fd6e 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -36,7 +36,7 @@ namespace Emby.Drawing private readonly IImageEncoder _imageEncoder; private readonly IMediaEncoder _mediaEncoder; - private bool _disposed = false; + private bool _disposed; /// /// Initializes a new instance of the class. @@ -466,11 +466,11 @@ namespace Emby.Drawing } /// - public void CreateImageCollage(ImageCollageOptions options) + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) { _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); - _imageEncoder.CreateImageCollage(options); + _imageEncoder.CreateImageCollage(options, libraryName); _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); } diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index bbb5c17162..2a1cfd3da5 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -38,7 +38,7 @@ namespace Emby.Drawing } /// - public void CreateImageCollage(ImageCollageOptions options) + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) { throw new NotImplementedException(); } diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 57302b5067..5f7e51858a 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -133,9 +133,20 @@ namespace Emby.Server.Implementations.Images protected virtual IEnumerable GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable items) { + var useBackdrop = primaryItem is CollectionFolder; return items .Select(i => { + // Use Backdrop instead of Primary image for Library images. + if (useBackdrop) + { + var backdrop = i.GetImageInfo(ImageType.Backdrop, 0); + if (backdrop != null && backdrop.IsLocalFile) + { + return backdrop.Path; + } + } + var image = i.GetImageInfo(ImageType.Primary, 0); if (image != null && image.IsLocalFile) { @@ -190,7 +201,7 @@ namespace Emby.Server.Implementations.Images return null; } - ImageProcessor.CreateImageCollage(options); + ImageProcessor.CreateImageCollage(options, primaryItem.Name); return outputPath; } diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index a1caa751b1..6a9dbdae42 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -553,13 +553,13 @@ namespace Jellyfin.Drawing.Skia } /// - public void CreateImageCollage(ImageCollageOptions options) + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) { double ratio = (double)options.Width / options.Height; if (ratio >= 1.4) { - new StripCollageBuilder(this).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + new StripCollageBuilder(this).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height, libraryName); } else if (ratio >= .9) { diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 10bb59648f..302df6fcb9 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -82,47 +82,53 @@ namespace Jellyfin.Drawing.Skia /// The path at which to place the resulting image. /// The desired width of the collage. /// The desired height of the collage. - public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) + /// The name of the library to draw on the collage. + public void BuildThumbCollage(string[] paths, string outputPath, int width, int height, string? libraryName) { - using var bitmap = BuildThumbCollageBitmap(paths, width, height); + using var bitmap = BuildThumbCollageBitmap(paths, width, height, libraryName); using var outputStream = new SKFileWStream(outputPath); using var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels()); pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); } - private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height) + private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height, string? libraryName) { var bitmap = new SKBitmap(width, height); using var canvas = new SKCanvas(bitmap); canvas.Clear(SKColors.Black); - // number of images used in the thumbnail - var iCount = 3; - - // determine sizes for each image that will composited into the final image - var iSlice = Convert.ToInt32(width / iCount); - int iHeight = Convert.ToInt32(height * 1.00); - int imageIndex = 0; - for (int i = 0; i < iCount; i++) + using var backdrop = GetNextValidImage(paths, 0, out _); + if (backdrop == null) { - using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex); - imageIndex = newIndex; - if (currentBitmap == null) - { - continue; - } + return bitmap; + } - // resize to the same aspect as the original - int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); - using var resizedImage = SkiaEncoder.ResizeImage(currentBitmap, new SKImageInfo(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace)); + // resize to the same aspect as the original + var backdropHeight = Math.Abs(width * backdrop.Height / backdrop.Width); + using var residedBackdrop = SkiaEncoder.ResizeImage(backdrop, new SKImageInfo(width, backdropHeight, backdrop.ColorType, backdrop.AlphaType, backdrop.ColorSpace)); + // draw the backdrop + canvas.DrawImage(residedBackdrop, 0, 0); - // crop image - int ix = Math.Abs((iWidth - iSlice) / 2); - using var subset = resizedImage.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)); - // draw image onto canvas - canvas.DrawImage(subset ?? resizedImage, iSlice * i, 0); - } + // draw shadow rectangle + var paintColor = new SKPaint + { + Color = SKColors.Black.WithAlpha(0x78), + Style = SKPaintStyle.Fill + }; + canvas.DrawRect(0, 0, width, height, paintColor); + + // draw library name + var textPaint = new SKPaint + { + Color = SKColors.White, + Style = SKPaintStyle.Fill, + TextSize = 56, + TextAlign = SKTextAlign.Center, + Typeface = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright), + IsAntialias = true + }; + canvas.DrawText(libraryName, width / 2f, height / 2f, textPaint); return bitmap; } diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index e09ccd204a..0f97f05688 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -61,6 +61,7 @@ namespace MediaBrowser.Controller.Drawing /// Create an image collage. /// /// The options to use when creating the collage. - void CreateImageCollage(ImageCollageOptions options); + /// Optional. + void CreateImageCollage(ImageCollageOptions options, string? libraryName); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 69d7991652..80687c9d04 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -85,7 +85,8 @@ namespace MediaBrowser.Controller.Drawing /// Creates the image collage. /// /// The options. - void CreateImageCollage(ImageCollageOptions options); + /// The library name to draw onto the collage. + void CreateImageCollage(ImageCollageOptions options, string? libraryName); bool SupportsTransparency(string path); } From d740e7aee6582d576c5bc2688df3c356203f5974 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 21 Aug 2020 20:36:56 +0200 Subject: [PATCH 015/203] Increase font size, center text --- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 302df6fcb9..09036b9f91 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -123,12 +123,12 @@ namespace Jellyfin.Drawing.Skia { Color = SKColors.White, Style = SKPaintStyle.Fill, - TextSize = 56, + TextSize = 112, TextAlign = SKTextAlign.Center, Typeface = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright), IsAntialias = true }; - canvas.DrawText(libraryName, width / 2f, height / 2f, textPaint); + canvas.DrawText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint); return bitmap; } From 9165dc3b3a94714da307079413f62326d5e585ef Mon Sep 17 00:00:00 2001 From: David Date: Sat, 22 Aug 2020 14:31:28 +0200 Subject: [PATCH 016/203] Scale down text if too long --- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 09036b9f91..0e94f87f6a 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -128,6 +128,14 @@ namespace Jellyfin.Drawing.Skia Typeface = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright), IsAntialias = true }; + + // scale down text to 90% of the width if text is larger than 95% of the width + var textWidth = textPaint.MeasureText(libraryName); + if (textWidth > width * 0.95) + { + textPaint.TextSize = 0.9f * width * textPaint.TextSize / textWidth; + } + canvas.DrawText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint); return bitmap; From 2a84d5a6933842e99b3b1f734e9a2b7241705a7f Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Mon, 31 Aug 2020 16:35:37 +0200 Subject: [PATCH 017/203] Enable nullable for interface --- MediaBrowser.Controller/Drawing/IImageEncoder.cs | 1 + MediaBrowser.Controller/Drawing/IImageProcessor.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 4e640d4215..770c6dc2d2 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#nullable enable using System; using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index cab73ed0c8..935a790312 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#nullable enable using System; using System.Collections.Generic; @@ -75,7 +76,7 @@ namespace MediaBrowser.Controller.Drawing /// /// The options. /// Task. - Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options); + Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options); /// /// Gets the supported image output formats. From 8a15ad160b30769305b5cf03e16b3cec742156de Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 5 Sep 2020 18:58:16 -0400 Subject: [PATCH 018/203] Fix plugin events not being called and clean up InstallationManager.cs --- .../Updates/InstallationManager.cs | 66 ++++++------------- .../Updates/IInstallationManager.cs | 23 ------- 2 files changed, 19 insertions(+), 70 deletions(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index f121a34938..ac3caf83e6 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -10,12 +10,15 @@ using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; @@ -34,6 +37,7 @@ namespace Emby.Server.Implementations.Updates /// private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; + private readonly IEventManager _eventManager; private readonly IHttpClientFactory _httpClientFactory; private readonly IJsonSerializer _jsonSerializer; private readonly IServerConfigurationManager _config; @@ -63,23 +67,20 @@ namespace Emby.Server.Implementations.Updates ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, + IEventManager eventManager, IHttpClientFactory httpClientFactory, IJsonSerializer jsonSerializer, IServerConfigurationManager config, IFileSystem fileSystem, IZipClient zipClient) { - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - _currentInstallations = new List<(InstallationInfo, CancellationTokenSource)>(); _completedInstallationsInternal = new ConcurrentBag(); _logger = logger; _applicationHost = appHost; _appPaths = appPaths; + _eventManager = eventManager; _httpClientFactory = httpClientFactory; _jsonSerializer = jsonSerializer; _config = config; @@ -87,27 +88,6 @@ namespace Emby.Server.Implementations.Updates _zipClient = zipClient; } - /// - public event EventHandler PackageInstalling; - - /// - public event EventHandler PackageInstallationCompleted; - - /// - public event EventHandler PackageInstallationFailed; - - /// - public event EventHandler PackageInstallationCancelled; - - /// - public event EventHandler PluginUninstalled; - - /// - public event EventHandler PluginUpdated; - - /// - public event EventHandler PluginInstalled; - /// public IEnumerable CompletedInstallations => _completedInstallationsInternal; @@ -256,11 +236,11 @@ namespace Emby.Server.Implementations.Updates var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token; - PackageInstalling?.Invoke(this, package); + await _eventManager.PublishAsync(new PluginInstallingEventArgs(package)).ConfigureAwait(false); try { - await InstallPackageInternal(package, linkedToken).ConfigureAwait(false); + var isUpdate = await InstallPackageInternal(package, linkedToken).ConfigureAwait(false); lock (_currentInstallationsLock) { @@ -268,8 +248,11 @@ namespace Emby.Server.Implementations.Updates } _completedInstallationsInternal.Add(package); + await _eventManager.PublishAsync(isUpdate + ? (GenericEventArgs)new PluginUpdatedEventArgs(package) + : new PluginInstalledEventArgs(package)).ConfigureAwait(false); - PackageInstallationCompleted?.Invoke(this, package); + _applicationHost.NotifyPendingRestart(); } catch (OperationCanceledException) { @@ -280,7 +263,7 @@ namespace Emby.Server.Implementations.Updates _logger.LogInformation("Package installation cancelled: {0} {1}", package.Name, package.Version); - PackageInstallationCancelled?.Invoke(this, package); + await _eventManager.PublishAsync(new PluginInstallationCancelledEventArgs(package)).ConfigureAwait(false); throw; } @@ -293,11 +276,11 @@ namespace Emby.Server.Implementations.Updates _currentInstallations.Remove(tuple); } - PackageInstallationFailed?.Invoke(this, new InstallationFailedEventArgs + await _eventManager.PublishAsync(new InstallationFailedEventArgs { InstallationInfo = package, Exception = ex - }); + }).ConfigureAwait(false); throw; } @@ -314,7 +297,7 @@ namespace Emby.Server.Implementations.Updates /// The package. /// The cancellation token. /// . - private async Task InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken) + private async Task InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken) { // Set last update time if we were installed before IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => p.Id == package.Guid) @@ -324,20 +307,9 @@ namespace Emby.Server.Implementations.Updates await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); // Do plugin-specific processing - if (plugin == null) - { - _logger.LogInformation("New plugin installed: {0} {1}", package.Name, package.Version); - - PluginInstalled?.Invoke(this, package); - } - else - { - _logger.LogInformation("Plugin updated: {0} {1}", package.Name, package.Version); + _logger.LogInformation(plugin == null ? "New plugin installed: {0} {1}" : "Plugin updated: {0} {1}", package.Name, package.Version); - PluginUpdated?.Invoke(this, package); - } - - _applicationHost.NotifyPendingRestart(); + return plugin != null; } private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken) @@ -438,7 +410,7 @@ namespace Emby.Server.Implementations.Updates _config.SaveConfiguration(); } - PluginUninstalled?.Invoke(this, plugin); + _eventManager.Publish(new PluginUninstalledEventArgs(plugin)); _applicationHost.NotifyPendingRestart(); } diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 4b4030bc27..1b38abb356 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -11,29 +11,6 @@ namespace MediaBrowser.Common.Updates { public interface IInstallationManager : IDisposable { - event EventHandler PackageInstalling; - - event EventHandler PackageInstallationCompleted; - - event EventHandler PackageInstallationFailed; - - event EventHandler PackageInstallationCancelled; - - /// - /// Occurs when a plugin is uninstalled. - /// - event EventHandler PluginUninstalled; - - /// - /// Occurs when a plugin is updated. - /// - event EventHandler PluginUpdated; - - /// - /// Occurs when a plugin is installed. - /// - event EventHandler PluginInstalled; - /// /// Gets the completed installations. /// From e3fb7f426c969c0316b53fe3348367c949f71c66 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 11 Sep 2020 16:08:49 -0600 Subject: [PATCH 019/203] Migrate subtitle upload to Jellyfin.Api --- .../Controllers/SubtitleController.cs | 28 ++ .../Models/SubtitleDtos/UploadSubtitleDto.cs | 34 ++ MediaBrowser.Api/Subtitles/SubtitleService.cs | 355 ------------------ 3 files changed, 62 insertions(+), 355 deletions(-) create mode 100644 Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs delete mode 100644 MediaBrowser.Api/Subtitles/SubtitleService.cs diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 78c9d43981..944da237bf 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; +using Jellyfin.Api.Models.SubtitleDtos; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; @@ -317,6 +318,33 @@ namespace Jellyfin.Api.Controllers return File(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8")); } + /// + /// Upload an external subtitle file. + /// + /// The item the subtitle belongs to. + /// The request body. + /// Subtitle uploaded. + /// A . + [HttpPost("Videos/{itemId}/Subtitles")] + public async Task UploadSubtitle( + [FromRoute, Required] Guid itemId, + [FromBody, Required] UploadSubtitleDto body) + { + var video = (Video)_libraryManager.GetItemById(itemId); + var data = Convert.FromBase64String(body.Data); + await using var memoryStream = new MemoryStream(data); + await _subtitleManager.UploadSubtitle( + video, + new SubtitleResponse + { + Format = body.Format, + Language = body.Language, + IsForced = body.IsForced, + Stream = memoryStream + }).ConfigureAwait(false); + return NoContent(); + } + /// /// Encodes a subtitle in the specified format. /// diff --git a/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs b/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs new file mode 100644 index 0000000000..30473255e8 --- /dev/null +++ b/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.SubtitleDtos +{ + /// + /// Upload subtitles dto. + /// + public class UploadSubtitleDto + { + /// + /// Gets or sets the subtitle language. + /// + [Required] + public string Language { get; set; } = string.Empty; + + /// + /// Gets or sets the subtitle format. + /// + [Required] + public string Format { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether the subtitle is forced. + /// + [Required] + public bool IsForced { get; set; } + + /// + /// Gets or sets the subtitle data. + /// + [Required] + public string Data { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs deleted file mode 100644 index ed2533f6d4..0000000000 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ /dev/null @@ -1,355 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using MimeTypes = MediaBrowser.Model.Net.MimeTypes; - -namespace MediaBrowser.Api.Subtitles -{ - [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")] - [Authenticated(Roles = "Admin")] - public class DeleteSubtitle - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")] - public int Index { get; set; } - } - - [Route("/Videos/{Id}/Subtitles", "POST", Summary = "Upload an external subtitle file")] - [Authenticated(Roles = "admin")] - public class PostSubtitle : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Language { get; set; } - - [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Format { get; set; } - - [ApiMember(Name = "IsForced", Description = "IsForced", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string IsForced { get; set; } - - [ApiMember(Name = "Data", Description = "Data", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Data { get; set; } - - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")] - [Authenticated] - public class SearchRemoteSubtitles : IReturn - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Language { get; set; } - - public bool? IsPerfectMatch { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")] - [Authenticated] - public class DownloadRemoteSubtitles : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } - - [ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SubtitleId { get; set; } - } - - [Route("/Providers/Subtitles/Subtitles/{Id}", "GET")] - [Authenticated] - public class GetRemoteSubtitles : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")] - [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/{StartPositionTicks}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")] - public class GetSubtitle - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - - [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Format { get; set; } - - [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public long StartPositionTicks { get; set; } - - [ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public long? EndPositionTicks { get; set; } - - [ApiMember(Name = "CopyTimestamps", Description = "CopyTimestamps", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool CopyTimestamps { get; set; } - public bool AddVttTimeMap { get; set; } - } - - [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")] - [Authenticated] - public class GetSubtitlePlaylist - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - - [ApiMember(Name = "SegmentLength", Description = "The subtitle srgment length", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")] - public int SegmentLength { get; set; } - } - - public class SubtitleService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly ISubtitleManager _subtitleManager; - private readonly ISubtitleEncoder _subtitleEncoder; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IProviderManager _providerManager; - private readonly IFileSystem _fileSystem; - private readonly IAuthorizationContext _authContext; - - public SubtitleService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILibraryManager libraryManager, - ISubtitleManager subtitleManager, - ISubtitleEncoder subtitleEncoder, - IMediaSourceManager mediaSourceManager, - IProviderManager providerManager, - IFileSystem fileSystem, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _libraryManager = libraryManager; - _subtitleManager = subtitleManager; - _subtitleEncoder = subtitleEncoder; - _mediaSourceManager = mediaSourceManager; - _providerManager = providerManager; - _fileSystem = fileSystem; - _authContext = authContext; - } - - public async Task Get(GetSubtitlePlaylist request) - { - var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); - - var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false); - - var builder = new StringBuilder(); - - var runtime = mediaSource.RunTimeTicks ?? -1; - - if (runtime <= 0) - { - throw new ArgumentException("HLS Subtitles are not supported for this media."); - } - - var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks; - if (segmentLengthTicks <= 0) - { - throw new ArgumentException("segmentLength was not given, or it was given incorrectly. (It should be bigger than 0)"); - } - - builder.AppendLine("#EXTM3U"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture)); - builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); - builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); - - long positionTicks = 0; - - var accessToken = _authContext.GetAuthorizationInfo(Request).Token; - - while (positionTicks < runtime) - { - var remaining = runtime - positionTicks; - var lengthTicks = Math.Min(remaining, segmentLengthTicks); - - builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture) + ","); - - var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); - - var url = string.Format("stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}", - positionTicks.ToString(CultureInfo.InvariantCulture), - endPositionTicks.ToString(CultureInfo.InvariantCulture), - accessToken); - - builder.AppendLine(url); - - positionTicks += segmentLengthTicks; - } - - builder.AppendLine("#EXT-X-ENDLIST"); - - return ResultFactory.GetResult(Request, builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); - } - - public async Task Get(GetSubtitle request) - { - if (string.Equals(request.Format, "js", StringComparison.OrdinalIgnoreCase)) - { - request.Format = "json"; - } - if (string.IsNullOrEmpty(request.Format)) - { - var item = (Video)_libraryManager.GetItemById(request.Id); - - var idString = request.Id.ToString("N", CultureInfo.InvariantCulture); - var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null) - .First(i => string.Equals(i.Id, request.MediaSourceId ?? idString)); - - var subtitleStream = mediaSource.MediaStreams - .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index); - - return await ResultFactory.GetStaticFileResult(Request, subtitleStream.Path).ConfigureAwait(false); - } - - if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap) - { - using var stream = await GetSubtitles(request).ConfigureAwait(false); - using var reader = new StreamReader(stream); - - var text = reader.ReadToEnd(); - - text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000"); - - return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format)); - } - - return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format)); - } - - private Task GetSubtitles(GetSubtitle request) - { - var item = _libraryManager.GetItemById(request.Id); - - return _subtitleEncoder.GetSubtitles(item, - request.MediaSourceId, - request.Index, - request.Format, - request.StartPositionTicks, - request.EndPositionTicks ?? 0, - request.CopyTimestamps, - CancellationToken.None); - } - - public async Task Get(SearchRemoteSubtitles request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - return await _subtitleManager.SearchSubtitles(video, request.Language, request.IsPerfectMatch, CancellationToken.None).ConfigureAwait(false); - } - - public Task Delete(DeleteSubtitle request) - { - var item = _libraryManager.GetItemById(request.Id); - return _subtitleManager.DeleteSubtitles(item, request.Index); - } - - public void Post(PostSubtitle request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - var bytes = Convert.FromBase64String(request.Data); - var memoryStream = new MemoryStream(bytes) - { - Position = 0 - }; - - Task.Run(async () => - { - try - { - await _subtitleManager.UploadSubtitle(video, new SubtitleResponse { - Format = request.Format, - Language = request.Language, - IsForced = Convert.ToBoolean(request.IsForced), - Stream = memoryStream - }).ConfigureAwait(false); - - _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error uploading subtitle"); - } - }); - } - - public async Task Get(GetRemoteSubtitles request) - { - var result = await _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).ConfigureAwait(false); - - return ResultFactory.GetResult(Request, result.Stream, MimeTypes.GetMimeType("file." + result.Format)); - } - - public void Post(DownloadRemoteSubtitles request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - Task.Run(async () => - { - try - { - await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None) - .ConfigureAwait(false); - - _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error downloading subtitles"); - } - }); - } - } -} From 97e948bbf6405c65d711aca6cd9d9febae95e8ff Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 11 Sep 2020 16:11:19 -0600 Subject: [PATCH 020/203] Fix added warnings --- MediaBrowser.Controller/Subtitles/ISubtitleManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 8537a7ef47..feb26bc101 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -56,6 +56,9 @@ namespace MediaBrowser.Controller.Subtitles /// /// Upload new subtitle. /// + /// The video the subtitle belongs to. + /// The subtitle response. + /// A representing the asynchronous operation. Task UploadSubtitle(Video video, SubtitleResponse response); /// From dfbe4413ff89aced08dae1222a82468e6d322b43 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 11 Sep 2020 16:13:15 -0600 Subject: [PATCH 021/203] Fix added warnings --- MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 30f390dcf5..f4a58775db 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -177,8 +177,7 @@ namespace MediaBrowser.Providers.Subtitles public async Task UploadSubtitle(Video video, SubtitleResponse response) { var libraryOptions = BaseItem.LibraryManager.GetLibraryOptions(video); - - await TrySaveSubtitle(video, libraryOptions, response); + await TrySaveSubtitle(video, libraryOptions, response).ConfigureAwait(false); } private async Task TrySaveSubtitle( From c41ed13b3dd875498b5784f11250ae7f421d94e2 Mon Sep 17 00:00:00 2001 From: Jim Cartlidge Date: Sun, 13 Sep 2020 14:36:10 +0100 Subject: [PATCH 022/203] Commenting. --- .../MediaReceiverRegistrar/ControlHandler.cs | 18 ++- .../MediaReceiverRegistrarService.cs | 13 +- .../MediaReceiverRegistrarXmlBuilder.cs | 122 ++++++++++-------- .../ServiceActionListBuilder.cs | 48 ++++++- Emby.Dlna/Service/BaseControlHandler.cs | 20 +-- 5 files changed, 140 insertions(+), 81 deletions(-) diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 8bf0cd961b..464f71a6f1 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Xml; @@ -10,8 +8,16 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar { + /// + /// Defines the . + /// public class ControlHandler : BaseControlHandler { + /// + /// Initializes a new instance of the class. + /// + /// The for use with the instance. + /// The for use with the instance. public ControlHandler(IServerConfigurationManager config, ILogger logger) : base(config, logger) { @@ -35,9 +41,17 @@ namespace Emby.Dlna.MediaReceiverRegistrar throw new ResourceNotFoundException("Unexpected control request name: " + methodName); } + /// + /// Records that the handle is authorized in the xml stream. + /// + /// The . private static void HandleIsAuthorized(XmlWriter xmlWriter) => xmlWriter.WriteElementString("Result", "1"); + /// + /// Records that the handle is validated in the xml stream. + /// + /// The . private static void HandleIsValidated(XmlWriter xmlWriter) => xmlWriter.WriteElementString("Result", "1"); } diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs index e6d845e1e4..a5aae515c4 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Net.Http; using System.Threading.Tasks; using Emby.Dlna.Service; @@ -8,10 +6,19 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar { + /// + /// Defines the . + /// public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar { private readonly IServerConfigurationManager _config; + /// + /// Initializes a new instance of the class. + /// + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. public MediaReceiverRegistrarService( ILogger logger, IHttpClientFactory httpClientFactory, @@ -24,7 +31,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar /// public string GetServiceXml() { - return new MediaReceiverRegistrarXmlBuilder().GetXml(); + return MediaReceiverRegistrarXmlBuilder.GetXml(); } /// diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs index 26994925d1..37840cd096 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs @@ -1,79 +1,89 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Emby.Dlna.Common; using Emby.Dlna.Service; +using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { - public class MediaReceiverRegistrarXmlBuilder + /// + /// Defines the . + /// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drmnd/5d37515e-7a63-4709-8258-8fd4e0ed4482. + /// + public static class MediaReceiverRegistrarXmlBuilder { - public string GetXml() + /// + /// Retrieves an XML description of the X_MS_MediaReceiverRegistrar. + /// + /// An XML representation of this service. + public static string GetXml() { - return new ServiceXmlBuilder().GetXml( - new ServiceActionListBuilder().GetActions(), - GetStateVariables()); + return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); } + /// + /// The a list of all the state variables for this invocation. + /// + /// The . private static IEnumerable GetStateVariables() { - var list = new List(); - - list.Add(new StateVariable + var list = new List { - Name = "AuthorizationGrantedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "AuthorizationGrantedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_DeviceID", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_DeviceID", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "AuthorizationDeniedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "AuthorizationDeniedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "ValidationSucceededUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "ValidationSucceededUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_RegistrationRespMsg", - DataType = "bin.base64", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_RegistrationRespMsg", + DataType = "bin.base64", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_RegistrationReqMsg", - DataType = "bin.base64", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_RegistrationReqMsg", + DataType = "bin.base64", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "ValidationRevokedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "ValidationRevokedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_Result", - DataType = "int", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_Result", + DataType = "int", + SendsEvents = false + } + }; return list; } diff --git a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs index 13545c6894..1dc9c79c14 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs @@ -1,13 +1,19 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Emby.Dlna.Common; +using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { - public class ServiceActionListBuilder + /// + /// Defines the . + /// + public static class ServiceActionListBuilder { - public IEnumerable GetActions() + /// + /// Returns a list of services that this instance provides. + /// + /// An . + public static IEnumerable GetActions() { return new[] { @@ -21,6 +27,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar }; } + /// + /// Returns the action details for "IsValidated". + /// + /// The . private static ServiceAction GetIsValidated() { var action = new ServiceAction @@ -43,6 +53,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// + /// Returns the action details for "IsAuthorized". + /// + /// The . private static ServiceAction GetIsAuthorized() { var action = new ServiceAction @@ -65,6 +79,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// + /// Returns the action details for "RegisterDevice". + /// + /// The . private static ServiceAction GetRegisterDevice() { var action = new ServiceAction @@ -87,6 +105,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// + /// Returns the action details for "GetValidationSucceededUpdateID". + /// + /// The . private static ServiceAction GetGetValidationSucceededUpdateID() { var action = new ServiceAction @@ -103,7 +125,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetAuthorizationDeniedUpdateID() + /// + /// Returns the action details for "GetGetAuthorizationDeniedUpdateID". + /// + /// The . + private static ServiceAction GetGetAuthorizationDeniedUpdateID() { var action = new ServiceAction { @@ -119,7 +145,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetValidationRevokedUpdateID() + /// + /// Returns the action details for "GetValidationRevokedUpdateID". + /// + /// The . + private static ServiceAction GetGetValidationRevokedUpdateID() { var action = new ServiceAction { @@ -135,7 +165,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetAuthorizationGrantedUpdateID() + /// + /// Returns the action details for "GetAuthorizationGrantedUpdateID". + /// + /// The . + private static ServiceAction GetGetAuthorizationGrantedUpdateID() { var action = new ServiceAction { diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index d160e33393..36903b5ea8 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -60,10 +60,8 @@ namespace Emby.Dlna.Service Async = true }; - using (var reader = XmlReader.Create(streamReader, readerSettings)) - { - requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false); - } + using var reader = XmlReader.Create(streamReader, readerSettings); + requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false); } Logger.LogDebug("Received control request {0}", requestInfo.LocalName); @@ -124,10 +122,8 @@ namespace Emby.Dlna.Service { if (!reader.IsEmptyElement) { - using (var subReader = reader.ReadSubtree()) - { - return await ParseBodyTagAsync(subReader).ConfigureAwait(false); - } + using var subReader = reader.ReadSubtree(); + return await ParseBodyTagAsync(subReader).ConfigureAwait(false); } else { @@ -170,11 +166,9 @@ namespace Emby.Dlna.Service if (!reader.IsEmptyElement) { - using (var subReader = reader.ReadSubtree()) - { - await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); - return result; - } + using var subReader = reader.ReadSubtree(); + await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); + return result; } else { From db07510017dc589da38698fd5e5f9afc55092334 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 17 Sep 2020 19:16:23 +0800 Subject: [PATCH 023/203] add tonemap for AMD AMF --- .../MediaEncoding/EncodingHelper.cs | 230 ++++++++++-------- 1 file changed, 128 insertions(+), 102 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2c30ca4588..790b26f699 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -452,11 +452,13 @@ namespace MediaBrowser.Controller.MediaEncoding var arg = new StringBuilder(); var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(videoDecoder); + var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isNvencHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); @@ -517,11 +519,12 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + && (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder) + || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder)) { var isColorDepth10 = IsColorDepth10(state); - if (isNvencHevcDecoder && isColorDepth10 + if (isColorDepth10 && _mediaEncoder.SupportsHwaccel("opencl") && encodingOptions.EnableTonemapping && !string.IsNullOrEmpty(state.VideoStream.VideoRange) @@ -1023,19 +1026,19 @@ namespace MediaBrowser.Controller.MediaEncoding && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; } - if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)) { - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; var videoStream = state.VideoStream; var isColorDepth10 = IsColorDepth10(state); - if (videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1 - && isColorDepth10 + if (isColorDepth10 && _mediaEncoder.SupportsHwaccel("opencl") && encodingOptions.EnableTonemapping && !string.IsNullOrEmpty(videoStream.VideoRange) @@ -1652,47 +1655,7 @@ namespace MediaBrowser.Controller.MediaEncoding var outputSizeParam = ReadOnlySpan.Empty; var request = state.BaseRequest; - outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); - - // All possible beginning of video filters - // Don't break the order - string[] beginOfOutputSizeParam = new[] - { - // for tonemap_opencl - "hwupload,tonemap_opencl", - - // hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux) - "hwupload=extra_hw_frames", - - // vpp_qsv - "vpp", - - // hwdownload,format=p010le (hardware decode + software encode for vaapi) - "hwdownload", - - // format=nv12|vaapi,hwupload,scale_vaapi - "format", - - // bwdif,scale=expr - "bwdif", - - // yadif,scale=expr - "yadif", - - // scale=expr - "scale" - }; - - var index = -1; - foreach (var param in beginOfOutputSizeParam) - { - index = outputSizeParam.IndexOf(param, StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - outputSizeParam = outputSizeParam.Slice(index); - break; - } - } + outputSizeParam = GetOutputSizeParamInternal(state, options, outputVideoCodec); var videoSizeParam = string.Empty; var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; @@ -2083,10 +2046,19 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam); } + public string GetOutputSizeParam( + EncodingJobInfo state, + EncodingOptions options, + string outputVideoCodec) + { + string filters = GetOutputSizeParamInternal(state, options, outputVideoCodec); + return string.IsNullOrEmpty(filters) ? string.Empty : " -vf \"" + filters + "\""; + } + /// /// If we're going to put a fixed size on the command line, this will calculate it. /// - public string GetOutputSizeParam( + public string GetOutputSizeParamInternal( EncodingJobInfo state, EncodingOptions options, string outputVideoCodec) @@ -2102,6 +2074,8 @@ namespace MediaBrowser.Controller.MediaEncoding var inputHeight = videoStream?.Height; var threeDFormat = state.MediaSource.Video3DFormat; + var isSwDecoder = string.IsNullOrEmpty(videoDecoder); + var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; @@ -2117,47 +2091,78 @@ namespace MediaBrowser.Controller.MediaEncoding // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30; - // Currently only with the use of NVENC decoder can we get a decent performance. - // Currently only the HEVC/H265 format is supported. - // NVIDIA Pascal and Turing or higher are recommended. - if (isNvdecHevcDecoder && isColorDepth10 - && _mediaEncoder.SupportsHwaccel("opencl") - && options.EnableTonemapping - && !string.IsNullOrEmpty(videoStream.VideoRange) - && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) - { - var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; + var isScalingInAdvance = false; + var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - if (options.TonemappingParam != 0) + if ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder) + || (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder)) + { + // Currently only with the use of NVENC decoder can we get a decent performance. + // Currently only the HEVC/H265 format is supported with NVDEC decoder. + // NVIDIA Pascal and Turing or higher are recommended. + // AMD Polaris and Vega or higher are recommended. + if (isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && options.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) { - parameters += ":param={4}"; - } + var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; - if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) - { - parameters += ":range={5}"; - } + if (options.TonemappingParam != 0) + { + parameters += ":param={4}"; + } - // Upload the HDR10 or HLG data to the OpenCL device, - // use tonemap_opencl filter for tone mapping, - // and then download the SDR data to memory. - filters.Add("hwupload"); - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - parameters, - options.TonemappingAlgorithm, - options.TonemappingDesat, - options.TonemappingThreshold, - options.TonemappingPeak, - options.TonemappingParam, - options.TonemappingRange)); - filters.Add("hwdownload"); - - if (hasGraphicalSubs || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true) - || string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) - { - filters.Add("format=nv12"); + if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) + { + parameters += ":range={5}"; + } + + if (isSwDecoder || isD3d11vaDecoder) + { + isScalingInAdvance = true; + // Add scaling filter before tonemapping filter for performance. + filters.AddRange( + GetScalingFilters( + state, + inputWidth, + inputHeight, + threeDFormat, + videoDecoder, + outputVideoCodec, + request.Width, + request.Height, + request.MaxWidth, + request.MaxHeight)); + // Convert to hardware pixel format p010 when using SW decoder. + filters.Add("format=p010"); + } + + // Upload the HDR10 or HLG data to the OpenCL device, + // use tonemap_opencl filter for tone mapping, + // and then download the SDR data to memory. + filters.Add("hwupload"); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + parameters, + options.TonemappingAlgorithm, + options.TonemappingDesat, + options.TonemappingThreshold, + options.TonemappingPeak, + options.TonemappingParam, + options.TonemappingRange)); + filters.Add("hwdownload"); + + if (isLibX264Encoder + || hasGraphicalSubs + || (isNvdecHevcDecoder && isDeinterlaceHevc) + || (!isNvdecHevcDecoder && isDeinterlaceH264 || isDeinterlaceHevc)) + { + filters.Add("format=nv12"); + } } } @@ -2202,7 +2207,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add hardware deinterlace filter before scaling filter - if (state.DeInterlace("h264", true) || state.DeInterlace("avc", true)) + if (isDeinterlaceH264) { if (isVaapiH264Encoder) { @@ -2215,10 +2220,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add software deinterlace filter before scaling filter - if ((state.DeInterlace("h264", true) - || state.DeInterlace("avc", true) - || state.DeInterlace("h265", true) - || state.DeInterlace("hevc", true)) + if ((isDeinterlaceH264 || isDeinterlaceHevc) && !isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder) @@ -2242,7 +2244,21 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr - filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); + if (!isScalingInAdvance) + { + filters.AddRange( + GetScalingFilters( + state, + inputWidth, + inputHeight, + threeDFormat, + videoDecoder, + outputVideoCodec, + request.Width, + request.Height, + request.MaxWidth, + request.MaxHeight)); + } // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642) if (isVaapiH264Encoder) @@ -2275,7 +2291,7 @@ namespace MediaBrowser.Controller.MediaEncoding { output += string.Format( CultureInfo.InvariantCulture, - " -vf \"{0}\"", + "{0}", string.Join(",", filters)); } @@ -3069,21 +3085,31 @@ namespace MediaBrowser.Controller.MediaEncoding var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1); var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va"); - if ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) + if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { - if (isLinux) + // Currently there is no AMF decoder on Linux, only have h264 encoder. + if (isDxvaSupported && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) { - return "-hwaccel vaapi"; - } + if (isWindows && isWindows8orLater) + { + return "-hwaccel d3d11va"; + } - if (isWindows && isWindows8orLater) - { - return "-hwaccel d3d11va"; + if (isWindows && !isWindows8orLater) + { + return "-hwaccel dxva2"; + } } + } - if (isWindows && !isWindows8orLater) + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + if (IsVaapiSupported(state) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) { - return "-hwaccel dxva2"; + if (isLinux) + { + return "-hwaccel vaapi"; + } } } From 4db5700e18b98cb34784930707dc4ee2833865b6 Mon Sep 17 00:00:00 2001 From: Ryan Petris Date: Wed, 23 Sep 2020 14:12:26 -0700 Subject: [PATCH 024/203] Don't take a lock if there's no intention to manipulate the list of open streams. Instead, use a ConcurrentDictionary so that, in those situations, thread-safe access to the dictionary is ensured. --- .../Library/MediaSourceManager.cs | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 67cf8bf5ba..376a155705 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Library private readonly ILocalizationManager _localizationManager; private readonly IApplicationPaths _appPaths; - private readonly Dictionary _openStreams = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); private IMediaSourceProvider[] _providers; @@ -582,29 +583,20 @@ namespace Emby.Server.Implementations.Library mediaSource.InferTotalBitrate(); } - public async Task GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) + public Task GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) { - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + var info = _openStreams.Values.FirstOrDefault(i => { - var info = _openStreams.Values.FirstOrDefault(i => + var liveStream = i as ILiveStream; + if (liveStream != null) { - var liveStream = i as ILiveStream; - if (liveStream != null) - { - return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase); - } + return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase); + } - return false; - }); + return false; + }); - return info as IDirectStreamProvider; - } - finally - { - _liveStreamSemaphore.Release(); - } + return Task.FromResult(info as IDirectStreamProvider); } public async Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) @@ -793,29 +785,20 @@ namespace Emby.Server.Implementations.Library return new Tuple(info.MediaSource, info as IDirectStreamProvider); } - private async Task GetLiveStreamInfo(string id, CancellationToken cancellationToken) + private Task GetLiveStreamInfo(string id, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException(nameof(id)); } - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + if (_openStreams.TryGetValue(id, out ILiveStream info)) { - if (_openStreams.TryGetValue(id, out ILiveStream info)) - { - return info; - } - else - { - throw new ResourceNotFoundException(); - } + return Task.FromResult(info); } - finally + else { - _liveStreamSemaphore.Release(); + return Task.FromException(new ResourceNotFoundException()); } } @@ -844,7 +827,7 @@ namespace Emby.Server.Implementations.Library if (liveStream.ConsumerCount <= 0) { - _openStreams.Remove(id); + _openStreams.TryRemove(id, out _); _logger.LogInformation("Closing live stream {0}", id); From 293237b714835fadd0a7c387815d146a2e4b810b Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 18:40:10 +0100 Subject: [PATCH 025/203] Update BaseControlHandler.cs --- Emby.Dlna/Service/BaseControlHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index d160e33393..ea17773b1a 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -150,7 +150,7 @@ namespace Emby.Dlna.Service } } - return new ControlRequestInfo(); + throw new EndOfStreamException("Stream ended but no body tag found."); } private async Task ParseBodyTagAsync(XmlReader reader) From 12b5f1127e0bef04173a9ad0f80b13e2ec86552f Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 18:46:20 +0100 Subject: [PATCH 026/203] Update DescriptionXmlBuilder.cs --- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 1f429d0de3..98945c2443 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -235,13 +235,13 @@ namespace Emby.Dlna.Server .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty)) .Append(""); builder.Append("") - .Append(BuildUrl(service.ScpdUrl, true)) + .Append(BuildUrl(service.ScpdUrl)) .Append(""); builder.Append("") - .Append(BuildUrl(service.ControlUrl, true)) + .Append(BuildUrl(service.ControlUrl)) .Append(""); builder.Append("") - .Append(BuildUrl(service.EventSubUrl, true)) + .Append(BuildUrl(service.EventSubUrl)) .Append(""); builder.Append(""); From f3a90bab477ed7008c2569dcd3d89f21a92558fb Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 18:46:52 +0100 Subject: [PATCH 027/203] Update DescriptionXmlBuilder.cs --- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 98945c2443..573f7adc0c 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -249,14 +249,8 @@ namespace Emby.Dlna.Server builder.Append(""); } - - /// - /// Builds a valid url for inclusion in the xml. - /// - /// Url to include. - /// Optional. When set to true, the absolute url is always used. - /// The url to use for the element. - private string BuildUrl(string url, bool absoluteUrl = false) + + private string BuildUrl(string ure) { if (string.IsNullOrEmpty(url)) { @@ -267,7 +261,7 @@ namespace Emby.Dlna.Server url = "/dlna/" + _serverUdn + "/" + url; - if (EnableAbsoluteUrls || absoluteUrl) + if (EnableAbsoluteUrls) { url = _serverAddress.TrimEnd('/') + url; } From a52ab69e13d2b924522a21fa4da98f38ec298bd1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 18:47:24 +0100 Subject: [PATCH 028/203] Update DescriptionXmlBuilder.cs --- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 573f7adc0c..bca9e81cd0 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -249,8 +249,8 @@ namespace Emby.Dlna.Server builder.Append(""); } - - private string BuildUrl(string ure) + + private string BuildUrl(string url) { if (string.IsNullOrEmpty(url)) { From 63571578ae04ec3b16f8cc5c5b2f9252de78aeda Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 19:44:16 +0100 Subject: [PATCH 029/203] Update BaseControlHandler.cs --- Emby.Dlna/Service/BaseControlHandler.cs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index ea17773b1a..39004d3944 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -155,7 +155,7 @@ namespace Emby.Dlna.Service private async Task ParseBodyTagAsync(XmlReader reader) { - var result = new ControlRequestInfo(); + string namespaceURI = null, localName = null; await reader.MoveToContentAsync().ConfigureAwait(false); await reader.ReadAsync().ConfigureAwait(false); @@ -165,11 +165,12 @@ namespace Emby.Dlna.Service { if (reader.NodeType == XmlNodeType.Element) { - result.LocalName = reader.LocalName; - result.NamespaceURI = reader.NamespaceURI; + localName = reader.LocalName; + namespaceURI = reader.NamespaceURI; if (!reader.IsEmptyElement) { + var result = new ControlRequestInfo(localName, namespaceURI); using (var subReader = reader.ReadSubtree()) { await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); @@ -187,7 +188,12 @@ namespace Emby.Dlna.Service } } - return result; + if (localName != null && namespaceURI != null) + { + return new ControlRequestInfo(localName, namespaceURI); + } + + throw new EndOfStreamException("Stream ended but no control found."); } private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary headers) @@ -234,11 +240,18 @@ namespace Emby.Dlna.Service private class ControlRequestInfo { + public ControlRequestInfo(string localName, string namespaceUri) + { + LocalName = localName; + NamespaceURI = namespaceUri; + Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + public string LocalName { get; set; } public string NamespaceURI { get; set; } - public Dictionary Headers { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + public Dictionary Headers { get; } } } } From 75677791275792e8dcfae1e9361b93af75443a75 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 19:51:37 +0100 Subject: [PATCH 030/203] Update BaseControlHandler.cs --- Emby.Dlna/Service/BaseControlHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 39004d3944..776882aa55 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -188,7 +188,7 @@ namespace Emby.Dlna.Service } } - if (localName != null && namespaceURI != null) + if (localName != null && namespaceURI != null) { return new ControlRequestInfo(localName, namespaceURI); } From 6ca313abc186d45aef8b6d949432a4c9ef9cc1d2 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 25 Sep 2020 23:59:17 +0200 Subject: [PATCH 031/203] Add ProgressiveFileStream --- .../Helpers/FileStreamResponseHelpers.cs | 5 +- Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 164 ++++++++++++++++++ 2 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 Jellyfin.Api/Helpers/ProgressiveFileStream.cs diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 6b516977e8..366301d3ee 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -123,9 +123,8 @@ namespace Jellyfin.Api.Helpers state.Dispose(); } - await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None) - .WriteToAsync(httpContext.Response.Body, CancellationToken.None).ConfigureAwait(false); - return new FileStreamResult(httpContext.Response.Body, contentType); + var stream = new ProgressiveFileStream(outputPath, job, transcodingJobHelper); + return new FileStreamResult(stream, contentType); } finally { diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs new file mode 100644 index 0000000000..e09f3dca94 --- /dev/null +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -0,0 +1,164 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Models.PlaybackDtos; +using MediaBrowser.Model.IO; + +namespace Jellyfin.Api.Helpers +{ + /// + /// A progressive file stream for transferring transcoded files as they are written to. + /// + public class ProgressiveFileStream : Stream + { + private readonly FileStream _fileStream; + private readonly TranscodingJobDto? _job; + private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly bool _allowAsyncFileRead; + private int _bytesWritten; + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The path to the transcoded file. + /// The transcoding job information. + /// The transcoding job helper. + public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper) + { + _job = job; + _transcodingJobHelper = transcodingJobHelper; + _bytesWritten = 0; + + var fileOptions = FileOptions.SequentialScan; + _allowAsyncFileRead = false; + + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + fileOptions |= FileOptions.Asynchronous; + _allowAsyncFileRead = true; + } + + _fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions); + } + + /// + public override bool CanRead => _fileStream.CanRead; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => false; + + /// + public override long Length => throw new NotSupportedException(); + + /// + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + /// + public override void Flush() + { + _fileStream.Flush(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + return _fileStream.Read(buffer, offset, count); + } + + /// + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var eofCount = 0; + const int EmptyReadLimit = 20; + + int totalBytesRead = 0; + int remainingBytesToRead = count; + + while (eofCount < EmptyReadLimit && remainingBytesToRead > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + int bytesRead; + if (_allowAsyncFileRead) + { + bytesRead = await _fileStream.ReadAsync(buffer, offset, remainingBytesToRead, cancellationToken).ConfigureAwait(false); + } + else + { + bytesRead = _fileStream.Read(buffer, offset, remainingBytesToRead); + } + + remainingBytesToRead -= bytesRead; + if (bytesRead > 0) + { + _bytesWritten += bytesRead; + totalBytesRead += bytesRead; + + if (_job != null) + { + _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); + } + } + + if (bytesRead == 0) + { + if (_job == null || _job.HasExited) + { + eofCount++; + } + + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; + } + } + + return totalBytesRead; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + /// + public override void SetLength(long value) + => throw new NotSupportedException(); + + /// + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + /// + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _fileStream.Dispose(); + + if (_job != null) + { + _transcodingJobHelper.OnTranscodeEndRequest(_job); + } + } + + _disposed = true; + } + } +} From 146cad61505147ee1e118135a396eb6ed3e0fc78 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 26 Sep 2020 19:03:23 +0200 Subject: [PATCH 032/203] Remove EOF counter --- Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index e09f3dca94..b3566b6f80 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -79,13 +79,10 @@ namespace Jellyfin.Api.Helpers /// public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - var eofCount = 0; - const int EmptyReadLimit = 20; - int totalBytesRead = 0; int remainingBytesToRead = count; - while (eofCount < EmptyReadLimit && remainingBytesToRead > 0) + while (remainingBytesToRead > 0) { cancellationToken.ThrowIfCancellationRequested(); int bytesRead; @@ -109,20 +106,15 @@ namespace Jellyfin.Api.Helpers _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); } } - - if (bytesRead == 0) + else { if (_job == null || _job.HasExited) { - eofCount++; + break; } await Task.Delay(100, cancellationToken).ConfigureAwait(false); } - else - { - eofCount = 0; - } } return totalBytesRead; @@ -148,17 +140,23 @@ namespace Jellyfin.Api.Helpers return; } - if (disposing) + try { - _fileStream.Dispose(); - - if (_job != null) + if (disposing) { - _transcodingJobHelper.OnTranscodeEndRequest(_job); + _fileStream.Dispose(); + + if (_job != null) + { + _transcodingJobHelper.OnTranscodeEndRequest(_job); + } } } - - _disposed = true; + finally + { + _disposed = true; + base.Dispose(disposing); + } } } } From 05fa95f149582b3de1881c42aaa0ac00408a9947 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sat, 26 Sep 2020 19:33:59 -0700 Subject: [PATCH 033/203] Increase scan speed for music libraries --- .../Resolvers/Audio/MusicAlbumResolver.cs | 66 ++++++++++--------- .../Resolvers/Audio/MusicArtistResolver.cs | 14 +++- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 79b6dded3b..ff485a18e7 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Emby.Naming.Audio; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -113,50 +115,50 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio IFileSystem fileSystem, ILibraryManager libraryManager) { + // check for audio files before digging down into directories + var firstAudioFile = list + .Where(fileSystemInfo => !fileSystemInfo.IsDirectory) + .FirstOrDefault(fileSystemInfo => libraryManager.IsAudioFile(fileSystemInfo.FullName)); + if (firstAudioFile != null) + { + // at least one audio file exists + return true; + } + + if (!allowSubfolders) + { + // not music since no audio file exists and we're not looking into subfolders + return false; + } + var discSubfolderCount = 0; var notMultiDisc = false; var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var parser = new AlbumParser(namingOptions); - foreach (var fileSystemInfo in list) + + var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory); + + var result = Parallel.ForEach(directories, (fileSystemInfo, state) => { - if (fileSystemInfo.IsDirectory) + var path = fileSystemInfo.FullName; + var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); + + if (hasMusic) { - if (allowSubfolders) + if (parser.IsMultiPart(path)) { - if (notMultiDisc) - { - continue; - } - - var path = fileSystemInfo.FullName; - var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); - - if (hasMusic) - { - if (parser.IsMultiPart(path)) - { - logger.LogDebug("Found multi-disc folder: " + path); - discSubfolderCount++; - } - else - { - // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album - notMultiDisc = true; - } - } + logger.LogDebug("Found multi-disc folder: " + path); + discSubfolderCount++; } - } - else - { - var fullName = fileSystemInfo.FullName; - - if (libraryManager.IsAudioFile(fullName)) + else { - return true; + // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album + notMultiDisc = true; + state.Stop(); } } - } + }); if (notMultiDisc) { diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 5f5cd0e928..e9e688fa67 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -94,7 +95,18 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); // If we contain an album assume we are an artist folder - return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null; + var directories = args.FileSystemChildren.Where(i => i.IsDirectory); + + var result = Parallel.ForEach(directories, (fileSystemInfo, state) => + { + if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, directoryService)) + { + // stop once we see a music album + state.Stop(); + } + }); + + return !result.IsCompleted ? new MusicArtist() : null; } } } From 75041e7f39f76a39fcfae15d6cd2aee510b4d599 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 12:56:12 -0700 Subject: [PATCH 034/203] interlocked increment --- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index ff485a18e7..d690a383d3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Emby.Naming.Audio; using MediaBrowser.Controller.Entities.Audio; @@ -132,7 +133,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio } var discSubfolderCount = 0; - var notMultiDisc = false; var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var parser = new AlbumParser(namingOptions); @@ -149,18 +149,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio if (parser.IsMultiPart(path)) { logger.LogDebug("Found multi-disc folder: " + path); - discSubfolderCount++; + Interlocked.Increment(ref discSubfolderCount); } else { // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album - notMultiDisc = true; state.Stop(); } } }); - if (notMultiDisc) + if (!result.IsCompleted) { return false; } From 3cfbe6e3401546a56abc826736578d9bfa054d99 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 13:28:19 -0700 Subject: [PATCH 035/203] better audio file check --- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index d690a383d3..18ceb5e761 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -117,10 +117,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio ILibraryManager libraryManager) { // check for audio files before digging down into directories - var firstAudioFile = list - .Where(fileSystemInfo => !fileSystemInfo.IsDirectory) - .FirstOrDefault(fileSystemInfo => libraryManager.IsAudioFile(fileSystemInfo.FullName)); - if (firstAudioFile != null) + var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && libraryManager.IsAudioFile(fileSystemInfo.FullName)); + if (foundAudioFile) { // at least one audio file exists return true; From 303eccaffeb42eb39b04f3ed372dc9fe84de5455 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 16:22:52 -0700 Subject: [PATCH 036/203] Fix InvalidOperationException in TvdbSeriesProvider --- .../Plugins/TheTvdb/TvdbSeriesProvider.cs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index ca9b1d738f..b637fc8b29 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb await _tvdbClientManager .GetSeriesByIdAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken) .ConfigureAwait(false); - MapSeriesToResult(result, seriesResult.Data, metadataLanguage); + await MapSeriesToResult(result, seriesResult.Data, metadataLanguage).ConfigureAwait(false); } catch (TvDbServerException e) { @@ -297,7 +297,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb return name.Trim(); } - private void MapSeriesToResult(MetadataResult result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage) + private async Task MapSeriesToResult(MetadataResult result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage) { Series series = result.Item; series.SetProviderId(MetadataProvider.Tvdb, tvdbSeries.Id.ToString()); @@ -340,20 +340,25 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb { try { - var episodeSummary = _tvdbClientManager - .GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).Result.Data; - var maxSeasonNumber = episodeSummary.AiredSeasons.Select(s => Convert.ToInt32(s)).Max(); - var episodeQuery = new EpisodeQuery + var episodeSummary = await _tvdbClientManager.GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).ConfigureAwait(false); + + if (episodeSummary.Data.AiredSeasons.Length > 0) { - AiredSeason = maxSeasonNumber - }; - var episodesPage = - _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).Result.Data; - result.Item.EndDate = episodesPage.Select(e => + var maxSeasonNumber = episodeSummary.Data.AiredSeasons.Select(s => Convert.ToInt32(s)).Max(); + var episodeQuery = new EpisodeQuery { - DateTime.TryParse(e.FirstAired, out var firstAired); - return firstAired; - }).Max(); + AiredSeason = maxSeasonNumber + }; + var episodesPage = await _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).ConfigureAwait(false); + + var episodeDates = episodesPage.Data + .Select(e => DateTime.TryParse(e.FirstAired, out var firstAired) ? firstAired : (DateTime?)null) + .Where(dt => dt.HasValue); + if (episodeDates.Any()) + { + result.Item.EndDate = episodeDates.Max().Value; + } + } } catch (TvDbServerException e) { From 881a7fa90805ac96d1e43c426c50dbe575297a9b Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 16:57:43 -0700 Subject: [PATCH 037/203] update based on suggestions --- .../Plugins/TheTvdb/TvdbSeriesProvider.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index b637fc8b29..cdb159c0f0 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net.Http; using System.Text; @@ -342,9 +343,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb { var episodeSummary = await _tvdbClientManager.GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).ConfigureAwait(false); - if (episodeSummary.Data.AiredSeasons.Length > 0) + if (episodeSummary.Data.AiredSeasons.Length != 0) { - var maxSeasonNumber = episodeSummary.Data.AiredSeasons.Select(s => Convert.ToInt32(s)).Max(); + var maxSeasonNumber = episodeSummary.Data.AiredSeasons.Max(s => Convert.ToInt32(s, CultureInfo.InvariantCulture)); var episodeQuery = new EpisodeQuery { AiredSeason = maxSeasonNumber @@ -353,10 +354,11 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb var episodeDates = episodesPage.Data .Select(e => DateTime.TryParse(e.FirstAired, out var firstAired) ? firstAired : (DateTime?)null) - .Where(dt => dt.HasValue); - if (episodeDates.Any()) + .Where(dt => dt.HasValue) + .ToList(); + if (episodeDates.Count != 0) { - result.Item.EndDate = episodeDates.Max().Value; + result.Item.EndDate = episodeDates.Max(); } } } From fd7a36932da16fb1e85160aaa0da792303a4e72f Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Mon, 28 Sep 2020 15:31:28 -0700 Subject: [PATCH 038/203] simplify EndDate query --- .../Plugins/TheTvdb/TvdbSeriesProvider.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index cdb159c0f0..b34e52235a 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -352,14 +352,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb }; var episodesPage = await _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).ConfigureAwait(false); - var episodeDates = episodesPage.Data + result.Item.EndDate = episodesPage.Data .Select(e => DateTime.TryParse(e.FirstAired, out var firstAired) ? firstAired : (DateTime?)null) - .Where(dt => dt.HasValue) - .ToList(); - if (episodeDates.Count != 0) - { - result.Item.EndDate = episodeDates.Max(); - } + .Max(); } } catch (TvDbServerException e) From 53d8023defe19ef943f72964d93dbed40b6f1180 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 30 Sep 2020 17:37:30 -0600 Subject: [PATCH 039/203] Update all on-disk plugins --- .../ApplicationHost.cs | 94 ++++++--------- .../Updates/InstallationManager.cs | 10 +- MediaBrowser.Common/Plugins/LocalPlugin.cs | 113 ++++++++++++++++++ .../IServerApplicationHost.cs | 10 +- 4 files changed, 162 insertions(+), 65 deletions(-) create mode 100644 MediaBrowser.Common/Plugins/LocalPlugin.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7a46fdf2e7..984ab41f0a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -30,7 +29,6 @@ using Emby.Server.Implementations.Cryptography; using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.Dto; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Library; @@ -258,8 +256,8 @@ namespace Emby.Server.Implementations IServiceCollection serviceCollection) { _xmlSerializer = new MyXmlSerializer(); - _jsonSerializer = new JsonSerializer(); - + _jsonSerializer = new JsonSerializer(); + ServiceCollection = serviceCollection; _networkManager = networkManager; @@ -1026,80 +1024,54 @@ namespace Emby.Server.Implementations protected abstract void RestartInternal(); - /// - /// Comparison function used in . - /// - /// Item to compare. - /// Item to compare with. - /// Boolean result of the operation. - private static int VersionCompare( - (Version PluginVersion, string Name, string Path) a, - (Version PluginVersion, string Name, string Path) b) - { - int compare = string.Compare(a.Name, b.Name, true, CultureInfo.InvariantCulture); - - if (compare == 0) - { - return a.PluginVersion.CompareTo(b.PluginVersion); - } - - return compare; - } - - /// - /// Returns a list of plugins to install. - /// - /// Path to check. - /// True if an attempt should be made to delete old plugs. - /// Enumerable list of dlls to load. - private IEnumerable GetPlugins(string path, bool cleanup = true) + /// + public IEnumerable GetLocalPlugins(string path, bool cleanup = true) { - var dllList = new List(); - var versions = new List<(Version PluginVersion, string Name, string Path)>(); + var minimumVersion = new Version(0, 0, 0, 1); + var versions = new List(); var directories = Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly); - string metafile; foreach (var dir in directories) { try { - metafile = Path.Combine(dir, "meta.json"); + var metafile = Path.Combine(dir, "meta.json"); if (File.Exists(metafile)) { var manifest = _jsonSerializer.DeserializeFromFile(metafile); if (!Version.TryParse(manifest.TargetAbi, out var targetAbi)) { - targetAbi = new Version(0, 0, 0, 1); + targetAbi = minimumVersion; } if (!Version.TryParse(manifest.Version, out var version)) { - version = new Version(0, 0, 0, 1); + version = minimumVersion; } if (ApplicationVersion >= targetAbi) { // Only load Plugins if the plugin is built for this version or below. - versions.Add((version, manifest.Name, dir)); + versions.Add(new LocalPlugin(manifest.Guid, manifest.Name, version, dir)); } } else { // No metafile, so lets see if the folder is versioned. metafile = dir.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)[^1]; - + int versionIndex = dir.LastIndexOf('_'); - if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version ver)) + if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version parsedVersion)) { // Versioned folder. - versions.Add((ver, metafile, dir)); + versions.Add(new LocalPlugin(Guid.Empty, metafile, parsedVersion, dir)); } else { - // Un-versioned folder - Add it under the path name and version 0.0.0.1. - versions.Add((new Version(0, 0, 0, 1), metafile, dir)); - } + // Un-versioned folder - Add it under the path name and version 0.0.0.1. + versions.Add(new LocalPlugin(Guid.Empty, metafile, minimumVersion, dir)); + } } } catch @@ -1109,14 +1081,14 @@ namespace Emby.Server.Implementations } string lastName = string.Empty; - versions.Sort(VersionCompare); + versions.Sort(LocalPlugin.Compare); // Traverse backwards through the list. // The first item will be the latest version. for (int x = versions.Count - 1; x >= 0; x--) { if (!string.Equals(lastName, versions[x].Name, StringComparison.OrdinalIgnoreCase)) { - dllList.AddRange(Directory.EnumerateFiles(versions[x].Path, "*.dll", SearchOption.AllDirectories)); + versions[x].DllFiles.AddRange(Directory.EnumerateFiles(versions[x].Path, "*.dll", SearchOption.AllDirectories)); lastName = versions[x].Name; continue; } @@ -1124,6 +1096,7 @@ namespace Emby.Server.Implementations if (!string.IsNullOrEmpty(lastName) && cleanup) { // Attempt a cleanup of old folders. + versions.RemoveAt(x); try { Logger.LogDebug("Deleting {Path}", versions[x].Path); @@ -1136,7 +1109,7 @@ namespace Emby.Server.Implementations } } - return dllList; + return versions; } /// @@ -1147,21 +1120,24 @@ namespace Emby.Server.Implementations { if (Directory.Exists(ApplicationPaths.PluginsPath)) { - foreach (var file in GetPlugins(ApplicationPaths.PluginsPath)) + foreach (var plugin in GetLocalPlugins(ApplicationPaths.PluginsPath)) { - Assembly plugAss; - try + foreach (var file in plugin.DllFiles) { - plugAss = Assembly.LoadFrom(file); - } - catch (FileLoadException ex) - { - Logger.LogError(ex, "Failed to load assembly {Path}", file); - continue; - } + Assembly plugAss; + try + { + plugAss = Assembly.LoadFrom(file); + } + catch (FileLoadException ex) + { + Logger.LogError(ex, "Failed to load assembly {Path}", file); + continue; + } - Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); - yield return plugAss; + Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); + yield return plugAss; + } } } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 003cf3c740..365d890655 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -15,14 +15,13 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; -using MediaBrowser.Common.System; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Logging; -using MediaBrowser.Model.System; namespace Emby.Server.Implementations.Updates { @@ -45,7 +44,7 @@ namespace Emby.Server.Implementations.Updates /// Gets the application host. /// /// The application host. - private readonly IApplicationHost _applicationHost; + private readonly IServerApplicationHost _applicationHost; private readonly IZipClient _zipClient; @@ -63,7 +62,7 @@ namespace Emby.Server.Implementations.Updates public InstallationManager( ILogger logger, - IApplicationHost appHost, + IServerApplicationHost appHost, IApplicationPaths appPaths, IHttpClientFactory httpClientFactory, IJsonSerializer jsonSerializer, @@ -237,7 +236,8 @@ namespace Emby.Server.Implementations.Updates private IEnumerable GetAvailablePluginUpdates(IReadOnlyList pluginCatalog) { - foreach (var plugin in _applicationHost.Plugins) + var plugins = _applicationHost.GetLocalPlugins(_appPaths.PluginsPath); + foreach (var plugin in plugins) { var compatibleVersions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, minVersion: plugin.Version); var version = compatibleVersions.FirstOrDefault(y => y.Version > plugin.Version); diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs new file mode 100644 index 0000000000..e26631615a --- /dev/null +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace MediaBrowser.Common.Plugins +{ + /// + /// Local plugin struct. + /// + public readonly struct LocalPlugin : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The plugin id. + /// The plugin name. + /// The plugin version. + /// The plugin path. + public LocalPlugin(Guid id, string name, Version version, string path) + { + Id = id; + Name = name; + Version = version; + Path = path; + DllFiles = new List(); + } + + /// + /// Gets the plugin id. + /// + public Guid Id { get; } + + /// + /// Gets the plugin name. + /// + public string Name { get; } + + /// + /// Gets the plugin version. + /// + public Version Version { get; } + + /// + /// Gets the plugin path. + /// + public string Path { get; } + + /// + /// Gets the list of dll files for this plugin. + /// + public List DllFiles { get; } + + /// + /// == operator. + /// + /// Left item. + /// Right item. + /// Comparison result. + public static bool operator ==(LocalPlugin left, LocalPlugin right) + { + return left.Equals(right); + } + + /// + /// != operator. + /// + /// Left item. + /// Right item. + /// Comparison result. + public static bool operator !=(LocalPlugin left, LocalPlugin right) + { + return !(left == right); + } + + /// + /// Compare two . + /// + /// The first item. + /// The second item. + /// Comparison result. + public static int Compare(LocalPlugin a, LocalPlugin b) + { + var compare = string.Compare(a.Name, b.Name, true, CultureInfo.InvariantCulture); + + // Id is not equal but name is. + if (a.Id != b.Id && compare == 0) + { + compare = a.Id.CompareTo(b.Id); + } + + return compare == 0 ? a.Version.CompareTo(b.Version) : compare; + } + + /// + public override bool Equals(object obj) + { + return obj is LocalPlugin other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return Name.GetHashCode(StringComparison.OrdinalIgnoreCase); + } + + /// + public bool Equals(LocalPlugin other) + { + return Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) + && Id.Equals(other.Id); + } + } +} diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index cfad17fb72..8a55437c5d 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -6,8 +6,8 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; +using MediaBrowser.Common.Plugins; using MediaBrowser.Model.System; -using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller { @@ -119,5 +119,13 @@ namespace MediaBrowser.Controller string ExpandVirtualPath(string path); string ReverseVirtualPath(string path); + + /// + /// Gets the list of local plugins. + /// + /// Plugin base directory. + /// Cleanup old plugins. + /// Enumerable of local plugins. + IEnumerable GetLocalPlugins(string path, bool cleanup = true); } } From 3b0c6c660d057336e26aa67cb36abaaccb00771d Mon Sep 17 00:00:00 2001 From: Justin LeCheminant Date: Wed, 30 Sep 2020 20:26:59 -0600 Subject: [PATCH 040/203] Removing string we don't use anymore. --- Emby.Server.Implementations/Localization/Core/af.json | 1 - Emby.Server.Implementations/Localization/Core/ar.json | 1 - Emby.Server.Implementations/Localization/Core/bg-BG.json | 1 - Emby.Server.Implementations/Localization/Core/bn.json | 1 - Emby.Server.Implementations/Localization/Core/ca.json | 1 - Emby.Server.Implementations/Localization/Core/cs.json | 1 - Emby.Server.Implementations/Localization/Core/da.json | 1 - Emby.Server.Implementations/Localization/Core/de.json | 1 - Emby.Server.Implementations/Localization/Core/el.json | 1 - Emby.Server.Implementations/Localization/Core/en-GB.json | 1 - Emby.Server.Implementations/Localization/Core/en-US.json | 1 - Emby.Server.Implementations/Localization/Core/es-AR.json | 1 - Emby.Server.Implementations/Localization/Core/es-MX.json | 1 - Emby.Server.Implementations/Localization/Core/es.json | 1 - Emby.Server.Implementations/Localization/Core/es_419.json | 1 - Emby.Server.Implementations/Localization/Core/es_DO.json | 1 - Emby.Server.Implementations/Localization/Core/fa.json | 1 - Emby.Server.Implementations/Localization/Core/fi.json | 1 - Emby.Server.Implementations/Localization/Core/fil.json | 1 - Emby.Server.Implementations/Localization/Core/fr-CA.json | 1 - Emby.Server.Implementations/Localization/Core/fr.json | 1 - Emby.Server.Implementations/Localization/Core/gsw.json | 1 - Emby.Server.Implementations/Localization/Core/he.json | 1 - Emby.Server.Implementations/Localization/Core/hr.json | 1 - Emby.Server.Implementations/Localization/Core/hu.json | 1 - Emby.Server.Implementations/Localization/Core/id.json | 1 - Emby.Server.Implementations/Localization/Core/is.json | 1 - Emby.Server.Implementations/Localization/Core/it.json | 1 - Emby.Server.Implementations/Localization/Core/ja.json | 1 - Emby.Server.Implementations/Localization/Core/kk.json | 1 - Emby.Server.Implementations/Localization/Core/ko.json | 1 - Emby.Server.Implementations/Localization/Core/lt-LT.json | 1 - Emby.Server.Implementations/Localization/Core/lv.json | 1 - Emby.Server.Implementations/Localization/Core/mk.json | 1 - Emby.Server.Implementations/Localization/Core/mr.json | 1 - Emby.Server.Implementations/Localization/Core/ms.json | 1 - Emby.Server.Implementations/Localization/Core/nb.json | 1 - Emby.Server.Implementations/Localization/Core/ne.json | 1 - Emby.Server.Implementations/Localization/Core/nl.json | 1 - Emby.Server.Implementations/Localization/Core/nn.json | 1 - Emby.Server.Implementations/Localization/Core/pl.json | 1 - Emby.Server.Implementations/Localization/Core/pt-BR.json | 1 - Emby.Server.Implementations/Localization/Core/pt-PT.json | 1 - Emby.Server.Implementations/Localization/Core/pt.json | 1 - Emby.Server.Implementations/Localization/Core/ro.json | 1 - Emby.Server.Implementations/Localization/Core/ru.json | 1 - Emby.Server.Implementations/Localization/Core/sk.json | 1 - Emby.Server.Implementations/Localization/Core/sl-SI.json | 1 - Emby.Server.Implementations/Localization/Core/sq.json | 1 - Emby.Server.Implementations/Localization/Core/sr.json | 1 - Emby.Server.Implementations/Localization/Core/sv.json | 1 - Emby.Server.Implementations/Localization/Core/ta.json | 1 - Emby.Server.Implementations/Localization/Core/th.json | 1 - Emby.Server.Implementations/Localization/Core/tr.json | 1 - Emby.Server.Implementations/Localization/Core/uk.json | 1 - Emby.Server.Implementations/Localization/Core/ur_PK.json | 1 - Emby.Server.Implementations/Localization/Core/vi.json | 1 - Emby.Server.Implementations/Localization/Core/zh-CN.json | 1 - Emby.Server.Implementations/Localization/Core/zh-HK.json | 1 - Emby.Server.Implementations/Localization/Core/zh-TW.json | 1 - 60 files changed, 60 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index d33e118931..977a1c2d70 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -85,7 +85,6 @@ "ItemAddedWithName": "{0} is in die versameling", "HomeVideos": "Tuis opnames", "HeaderRecordingGroups": "Groep Opnames", - "HeaderCameraUploads": "Kamera Oplaai", "Genres": "Genres", "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}", "ChapterNameValue": "Hoofstuk", diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 4eac8e75de..4b898e6fe0 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -16,7 +16,6 @@ "Folders": "المجلدات", "Genres": "التضنيفات", "HeaderAlbumArtists": "فناني الألبومات", - "HeaderCameraUploads": "تحميلات الكاميرا", "HeaderContinueWatching": "استئناف", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index 3fc7c7dc0f..1fed832768 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -16,7 +16,6 @@ "Folders": "Папки", "Genres": "Жанрове", "HeaderAlbumArtists": "Изпълнители на албуми", - "HeaderCameraUploads": "Качени от камера", "HeaderContinueWatching": "Продължаване на гледането", "HeaderFavoriteAlbums": "Любими албуми", "HeaderFavoriteArtists": "Любими изпълнители", diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index 1bd1909827..5667bf337e 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -14,7 +14,6 @@ "HeaderFavoriteArtists": "প্রিয় শিল্পীরা", "HeaderFavoriteAlbums": "প্রিয় এলবামগুলো", "HeaderContinueWatching": "দেখতে থাকুন", - "HeaderCameraUploads": "ক্যামেরার আপলোড সমূহ", "HeaderAlbumArtists": "এলবাম শিল্পী", "Genres": "জেনার", "Folders": "ফোল্ডারগুলো", diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index 2c802a39ef..b7852eccb3 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -16,7 +16,6 @@ "Folders": "Carpetes", "Genres": "Gèneres", "HeaderAlbumArtists": "Artistes del Àlbum", - "HeaderCameraUploads": "Pujades de Càmera", "HeaderContinueWatching": "Continua Veient", "HeaderFavoriteAlbums": "Àlbums Preferits", "HeaderFavoriteArtists": "Artistes Preferits", diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 464ca28ca0..b34fad066a 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -16,7 +16,6 @@ "Folders": "Složky", "Genres": "Žánry", "HeaderAlbumArtists": "Umělci alba", - "HeaderCameraUploads": "Nahrané fotografie", "HeaderContinueWatching": "Pokračovat ve sledování", "HeaderFavoriteAlbums": "Oblíbená alba", "HeaderFavoriteArtists": "Oblíbení interpreti", diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index f5397b62cd..b29ad94eff 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -16,7 +16,6 @@ "Folders": "Mapper", "Genres": "Genrer", "HeaderAlbumArtists": "Albumkunstnere", - "HeaderCameraUploads": "Kamera Uploads", "HeaderContinueWatching": "Fortsæt Afspilning", "HeaderFavoriteAlbums": "Favoritalbummer", "HeaderFavoriteArtists": "Favoritkunstnere", diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index fcbe9566e2..97ad1694a6 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -16,7 +16,6 @@ "Folders": "Verzeichnisse", "Genres": "Genres", "HeaderAlbumArtists": "Album-Interpreten", - "HeaderCameraUploads": "Kamera-Uploads", "HeaderContinueWatching": "Fortsetzen", "HeaderFavoriteAlbums": "Lieblingsalben", "HeaderFavoriteArtists": "Lieblings-Interpreten", diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 0753ea39d4..c45cc11cb8 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -16,7 +16,6 @@ "Folders": "Φάκελοι", "Genres": "Είδη", "HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ", - "HeaderCameraUploads": "Μεταφορτώσεις Κάμερας", "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 544c38cfa9..57ff13219f 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -16,7 +16,6 @@ "Folders": "Folders", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", "HeaderContinueWatching": "Continue Watching", "HeaderFavoriteAlbums": "Favourite Albums", "HeaderFavoriteArtists": "Favourite Artists", diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 97a843160e..92c54fb0e6 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -16,7 +16,6 @@ "Folders": "Folders", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", "HeaderContinueWatching": "Continue Watching", "HeaderFavoriteAlbums": "Favorite Albums", "HeaderFavoriteArtists": "Favorite Artists", diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index ac96c788c1..390074cdd7 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -16,7 +16,6 @@ "Folders": "Carpetas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas de álbum", - "HeaderCameraUploads": "Subidas de cámara", "HeaderContinueWatching": "Seguir viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 4ba324aa1b..ab54c0ea6a 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -16,7 +16,6 @@ "Folders": "Carpetas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas del álbum", - "HeaderCameraUploads": "Subidas desde la cámara", "HeaderContinueWatching": "Continuar viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index e7bd3959bf..d4e0b299dd 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -16,7 +16,6 @@ "Folders": "Carpetas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas del álbum", - "HeaderCameraUploads": "Subidas desde la cámara", "HeaderContinueWatching": "Continuar viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json index 0959ef2ca0..dcd30694f0 100644 --- a/Emby.Server.Implementations/Localization/Core/es_419.json +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -105,7 +105,6 @@ "Inherit": "Heredar", "HomeVideos": "Videos caseros", "HeaderRecordingGroups": "Grupos de grabación", - "HeaderCameraUploads": "Subidas desde la cámara", "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", "DeviceOnlineWithName": "{0} está conectado", "DeviceOfflineWithName": "{0} se ha desconectado", diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json index 26732eb3f1..b64ffbfbba 100644 --- a/Emby.Server.Implementations/Localization/Core/es_DO.json +++ b/Emby.Server.Implementations/Localization/Core/es_DO.json @@ -12,7 +12,6 @@ "Application": "Aplicación", "AppDeviceValues": "App: {0}, Dispositivo: {1}", "HeaderContinueWatching": "Continuar Viendo", - "HeaderCameraUploads": "Subidas de Cámara", "HeaderAlbumArtists": "Artistas del Álbum", "Genres": "Géneros", "Folders": "Carpetas", diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 500c292170..1986decf03 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -16,7 +16,6 @@ "Folders": "پوشه‌ها", "Genres": "ژانرها", "HeaderAlbumArtists": "هنرمندان آلبوم", - "HeaderCameraUploads": "آپلودهای دوربین", "HeaderContinueWatching": "ادامه تماشا", "HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه", "HeaderFavoriteArtists": "هنرمندان مورد علاقه", diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index f8d6e0e09b..aae22583ab 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -24,7 +24,6 @@ "HeaderFavoriteSongs": "Lempikappaleet", "HeaderFavoriteShows": "Lempisarjat", "HeaderFavoriteEpisodes": "Lempijaksot", - "HeaderCameraUploads": "Kamerasta Lähetetyt", "HeaderFavoriteArtists": "Lempiartistit", "HeaderFavoriteAlbums": "Lempialbumit", "HeaderContinueWatching": "Jatka katsomista", diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json index 47daf20442..1a3e188327 100644 --- a/Emby.Server.Implementations/Localization/Core/fil.json +++ b/Emby.Server.Implementations/Localization/Core/fil.json @@ -73,7 +73,6 @@ "HeaderFavoriteArtists": "Paboritong Artista", "HeaderFavoriteAlbums": "Paboritong Albums", "HeaderContinueWatching": "Ituloy Manood", - "HeaderCameraUploads": "Camera Uploads", "HeaderAlbumArtists": "Artista ng Album", "Genres": "Kategorya", "Folders": "Folders", diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index cd1c8144fd..3d7592e3c8 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -16,7 +16,6 @@ "Folders": "Dossiers", "Genres": "Genres", "HeaderAlbumArtists": "Artistes de l'album", - "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes favoris", diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 7fc9968219..f4ca8575a1 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -16,7 +16,6 @@ "Folders": "Dossiers", "Genres": "Genres", "HeaderAlbumArtists": "Artistes", - "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes préférés", diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json index 8780a884ba..ee1f8775e5 100644 --- a/Emby.Server.Implementations/Localization/Core/gsw.json +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -16,7 +16,6 @@ "Folders": "Ordner", "Genres": "Genres", "HeaderAlbumArtists": "Album-Künstler", - "HeaderCameraUploads": "Kamera-Uploads", "HeaderContinueWatching": "weiter schauen", "HeaderFavoriteAlbums": "Lieblingsalben", "HeaderFavoriteArtists": "Lieblings-Künstler", diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index dc3a981540..f906d6e117 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -16,7 +16,6 @@ "Folders": "תיקיות", "Genres": "ז'אנרים", "HeaderAlbumArtists": "אמני האלבום", - "HeaderCameraUploads": "העלאות ממצלמה", "HeaderContinueWatching": "המשך לצפות", "HeaderFavoriteAlbums": "אלבומים מועדפים", "HeaderFavoriteArtists": "אמנים מועדפים", diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index 97c77017b5..a3c936240d 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -16,7 +16,6 @@ "Folders": "Mape", "Genres": "Žanrovi", "HeaderAlbumArtists": "Izvođači na albumu", - "HeaderCameraUploads": "Uvoz sa kamere", "HeaderContinueWatching": "Nastavi gledati", "HeaderFavoriteAlbums": "Omiljeni albumi", "HeaderFavoriteArtists": "Omiljeni izvođači", diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index c5c3844e3f..343d213d41 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -16,7 +16,6 @@ "Folders": "Könyvtárak", "Genres": "Műfajok", "HeaderAlbumArtists": "Album előadók", - "HeaderCameraUploads": "Kamera feltöltések", "HeaderContinueWatching": "Megtekintés folytatása", "HeaderFavoriteAlbums": "Kedvenc albumok", "HeaderFavoriteArtists": "Kedvenc előadók", diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json index 585fc6f027..ef3ed2580a 100644 --- a/Emby.Server.Implementations/Localization/Core/id.json +++ b/Emby.Server.Implementations/Localization/Core/id.json @@ -20,7 +20,6 @@ "HeaderFavoriteArtists": "Artis Favorit", "HeaderFavoriteAlbums": "Album Favorit", "HeaderContinueWatching": "Lanjut Menonton", - "HeaderCameraUploads": "Unggahan Kamera", "HeaderAlbumArtists": "Album Artis", "Genres": "Aliran", "Folders": "Folder", diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json index 0f0f9130b0..0f769eaada 100644 --- a/Emby.Server.Implementations/Localization/Core/is.json +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -13,7 +13,6 @@ "HeaderFavoriteArtists": "Uppáhalds Listamenn", "HeaderFavoriteAlbums": "Uppáhalds Plötur", "HeaderContinueWatching": "Halda áfram að horfa", - "HeaderCameraUploads": "Myndavéla upphal", "HeaderAlbumArtists": "Höfundur plötu", "Genres": "Tegundir", "Folders": "Möppur", diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index bf1a0ef136..0a6238578d 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -16,7 +16,6 @@ "Folders": "Cartelle", "Genres": "Generi", "HeaderAlbumArtists": "Artisti degli Album", - "HeaderCameraUploads": "Caricamenti Fotocamera", "HeaderContinueWatching": "Continua a guardare", "HeaderFavoriteAlbums": "Album Preferiti", "HeaderFavoriteArtists": "Artisti Preferiti", diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index a4d9f9ef6b..35004f0eb4 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -16,7 +16,6 @@ "Folders": "フォルダー", "Genres": "ジャンル", "HeaderAlbumArtists": "アルバムアーティスト", - "HeaderCameraUploads": "カメラアップロード", "HeaderContinueWatching": "視聴を続ける", "HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteArtists": "お気に入りのアーティスト", diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 5618ff4a8f..91c1fb15bc 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -16,7 +16,6 @@ "Folders": "Qaltalar", "Genres": "Janrlar", "HeaderAlbumArtists": "Álbom oryndaýshylary", - "HeaderCameraUploads": "Kameradan júktelgender", "HeaderContinueWatching": "Qaraýdy jalǵastyrý", "HeaderFavoriteAlbums": "Tańdaýly álbomdar", "HeaderFavoriteArtists": "Tańdaýly oryndaýshylar", diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index a33953c273..fb01e46451 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -16,7 +16,6 @@ "Folders": "폴더", "Genres": "장르", "HeaderAlbumArtists": "앨범 아티스트", - "HeaderCameraUploads": "카메라 업로드", "HeaderContinueWatching": "계속 시청하기", "HeaderFavoriteAlbums": "즐겨찾는 앨범", "HeaderFavoriteArtists": "즐겨찾는 아티스트", diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 35053766b4..d4cb592efc 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -16,7 +16,6 @@ "Folders": "Katalogai", "Genres": "Žanrai", "HeaderAlbumArtists": "Albumo atlikėjai", - "HeaderCameraUploads": "Kameros", "HeaderContinueWatching": "Žiūrėti toliau", "HeaderFavoriteAlbums": "Mėgstami Albumai", "HeaderFavoriteArtists": "Mėgstami Atlikėjai", diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json index dbcf172871..5e3acfbe96 100644 --- a/Emby.Server.Implementations/Localization/Core/lv.json +++ b/Emby.Server.Implementations/Localization/Core/lv.json @@ -72,7 +72,6 @@ "ItemAddedWithName": "{0} tika pievienots bibliotēkai", "HeaderLiveTV": "Tiešraides TV", "HeaderContinueWatching": "Turpināt Skatīšanos", - "HeaderCameraUploads": "Kameras augšupielādes", "HeaderAlbumArtists": "Albumu Izpildītāji", "Genres": "Žanri", "Folders": "Mapes", diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json index bbdf99abab..b780ef498a 100644 --- a/Emby.Server.Implementations/Localization/Core/mk.json +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -51,7 +51,6 @@ "HeaderFavoriteArtists": "Омилени Изведувачи", "HeaderFavoriteAlbums": "Омилени Албуми", "HeaderContinueWatching": "Продолжи со гледање", - "HeaderCameraUploads": "Поставувања од камера", "HeaderAlbumArtists": "Изведувачи од Албуми", "Genres": "Жанрови", "Folders": "Папки", diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json index b6db2b0f29..fdb4171b53 100644 --- a/Emby.Server.Implementations/Localization/Core/mr.json +++ b/Emby.Server.Implementations/Localization/Core/mr.json @@ -54,7 +54,6 @@ "ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले", "HomeVideos": "घरचे व्हिडीयो", "HeaderRecordingGroups": "रेकॉर्डिंग गट", - "HeaderCameraUploads": "कॅमेरा अपलोड", "CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे", "Application": "अ‍ॅप्लिकेशन", "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}", diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 7f8df12895..5e3d095ff1 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -16,7 +16,6 @@ "Folders": "Fail-fail", "Genres": "Genre-genre", "HeaderAlbumArtists": "Album Artis-artis", - "HeaderCameraUploads": "Muatnaik Kamera", "HeaderContinueWatching": "Terus Menonton", "HeaderFavoriteAlbums": "Album-album Kegemaran", "HeaderFavoriteArtists": "Artis-artis Kegemaran", diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 07a5991211..245c3cd636 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -16,7 +16,6 @@ "Folders": "Mapper", "Genres": "Sjangre", "HeaderAlbumArtists": "Albumartister", - "HeaderCameraUploads": "Kameraopplastinger", "HeaderContinueWatching": "Fortsett å se", "HeaderFavoriteAlbums": "Favorittalbum", "HeaderFavoriteArtists": "Favorittartister", diff --git a/Emby.Server.Implementations/Localization/Core/ne.json b/Emby.Server.Implementations/Localization/Core/ne.json index 38c0737098..8e820d40c7 100644 --- a/Emby.Server.Implementations/Localization/Core/ne.json +++ b/Emby.Server.Implementations/Localization/Core/ne.json @@ -41,7 +41,6 @@ "HeaderFavoriteArtists": "मनपर्ने कलाकारहरू", "HeaderFavoriteAlbums": "मनपर्ने एल्बमहरू", "HeaderContinueWatching": "हेर्न जारी राख्नुहोस्", - "HeaderCameraUploads": "क्यामेरा अपलोडहरू", "HeaderAlbumArtists": "एल्बमका कलाकारहरू", "Genres": "विधाहरू", "Folders": "फोल्डरहरू", diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 41c74d54de..e102b92b90 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -16,7 +16,6 @@ "Folders": "Mappen", "Genres": "Genres", "HeaderAlbumArtists": "Albumartiesten", - "HeaderCameraUploads": "Camera-uploads", "HeaderContinueWatching": "Kijken hervatten", "HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteArtists": "Favoriete artiesten", diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json index fb6e81beb3..6236515b2c 100644 --- a/Emby.Server.Implementations/Localization/Core/nn.json +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -19,7 +19,6 @@ "HeaderFavoriteArtists": "Favoritt Artistar", "HeaderFavoriteAlbums": "Favoritt Album", "HeaderContinueWatching": "Fortsett å sjå", - "HeaderCameraUploads": "Kamera Opplastingar", "HeaderAlbumArtists": "Album Artist", "Genres": "Sjangrar", "Folders": "Mapper", diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index bdc0d0169b..003e591b37 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -16,7 +16,6 @@ "Folders": "Foldery", "Genres": "Gatunki", "HeaderAlbumArtists": "Wykonawcy albumów", - "HeaderCameraUploads": "Przekazane obrazy", "HeaderContinueWatching": "Kontynuuj odtwarzanie", "HeaderFavoriteAlbums": "Ulubione albumy", "HeaderFavoriteArtists": "Ulubieni wykonawcy", diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 275195640b..5e49ca702e 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -16,7 +16,6 @@ "Folders": "Pastas", "Genres": "Gêneros", "HeaderAlbumArtists": "Artistas do Álbum", - "HeaderCameraUploads": "Envios da Câmera", "HeaderContinueWatching": "Continuar Assistindo", "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteArtists": "Artistas favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index c1fb65743d..8aa1ed5497 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -16,7 +16,6 @@ "Folders": "Pastas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas do Álbum", - "HeaderCameraUploads": "Envios a partir da câmara", "HeaderContinueWatching": "Continuar a Ver", "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteArtists": "Artistas Favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index b534d0bbeb..2079940cd1 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -83,7 +83,6 @@ "Playlists": "Listas de Reprodução", "Photos": "Fotografias", "Movies": "Filmes", - "HeaderCameraUploads": "Carregamentos a partir da câmara", "FailedLoginAttemptWithUserName": "Tentativa de ligação falhada a partir de {0}", "DeviceOnlineWithName": "{0} está connectado", "DeviceOfflineWithName": "{0} desconectou-se", diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json index 699dd26daa..bc008df3b4 100644 --- a/Emby.Server.Implementations/Localization/Core/ro.json +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -74,7 +74,6 @@ "HeaderFavoriteArtists": "Artiști Favoriți", "HeaderFavoriteAlbums": "Albume Favorite", "HeaderContinueWatching": "Vizionează în continuare", - "HeaderCameraUploads": "Incărcări Cameră Foto", "HeaderAlbumArtists": "Album Artiști", "Genres": "Genuri", "Folders": "Dosare", diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 648aa384b0..95b93afb82 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -16,7 +16,6 @@ "Folders": "Папки", "Genres": "Жанры", "HeaderAlbumArtists": "Исполнители альбома", - "HeaderCameraUploads": "Камеры", "HeaderContinueWatching": "Продолжение просмотра", "HeaderFavoriteAlbums": "Избранные альбомы", "HeaderFavoriteArtists": "Избранные исполнители", diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index 0ee652637c..8e50269444 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -16,7 +16,6 @@ "Folders": "Priečinky", "Genres": "Žánre", "HeaderAlbumArtists": "Umelci albumu", - "HeaderCameraUploads": "Nahrané fotografie", "HeaderContinueWatching": "Pokračovať v pozeraní", "HeaderFavoriteAlbums": "Obľúbené albumy", "HeaderFavoriteArtists": "Obľúbení umelci", diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index 329c562e73..ff4b9e84fd 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -16,7 +16,6 @@ "Folders": "Mape", "Genres": "Zvrsti", "HeaderAlbumArtists": "Izvajalci albuma", - "HeaderCameraUploads": "Posnetki kamere", "HeaderContinueWatching": "Nadaljuj gledanje", "HeaderFavoriteAlbums": "Priljubljeni albumi", "HeaderFavoriteArtists": "Priljubljeni izvajalci", diff --git a/Emby.Server.Implementations/Localization/Core/sq.json b/Emby.Server.Implementations/Localization/Core/sq.json index 347ba5f970..ecca5af4cc 100644 --- a/Emby.Server.Implementations/Localization/Core/sq.json +++ b/Emby.Server.Implementations/Localization/Core/sq.json @@ -96,7 +96,6 @@ "HeaderFavoriteArtists": "Artistët e preferuar", "HeaderFavoriteAlbums": "Albumet e preferuar", "HeaderContinueWatching": "Vazhdo të shikosh", - "HeaderCameraUploads": "Ngarkimet nga Kamera", "HeaderAlbumArtists": "Artistët e albumeve", "Genres": "Zhanre", "Folders": "Dosje", diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index 5f3cbb1c8b..2b1eccfaf3 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -74,7 +74,6 @@ "HeaderFavoriteArtists": "Омиљени извођачи", "HeaderFavoriteAlbums": "Омиљени албуми", "HeaderContinueWatching": "Настави гледање", - "HeaderCameraUploads": "Слања са камере", "HeaderAlbumArtists": "Извођачи албума", "Genres": "Жанрови", "Folders": "Фасцикле", diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index c8662b2cab..12fda8a986 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -16,7 +16,6 @@ "Folders": "Mappar", "Genres": "Genrer", "HeaderAlbumArtists": "Albumartister", - "HeaderCameraUploads": "Kamerauppladdningar", "HeaderContinueWatching": "Fortsätt kolla", "HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteArtists": "Favoritartister", diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index 810b1b9abe..ae38f45e1c 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -20,7 +20,6 @@ "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது", "Inherit": "மரபுரிமையாகப் பெறு", "HeaderRecordingGroups": "பதிவு குழுக்கள்", - "HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்", "Folders": "கோப்புறைகள்", "FailedLoginAttemptWithUserName": "{0} இலிருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது", "DeviceOnlineWithName": "{0} இணைக்கப்பட்டது", diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json index 3b77215a30..71dd2c7a30 100644 --- a/Emby.Server.Implementations/Localization/Core/th.json +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -50,7 +50,6 @@ "HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ", "HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ", "HeaderContinueWatching": "ดูต่อ", - "HeaderCameraUploads": "อัปโหลดรูปถ่าย", "HeaderAlbumArtists": "อัลบั้มศิลปิน", "Genres": "ประเภท", "Folders": "โฟลเดอร์", diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 3cf3482eba..1e5f2cf198 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -16,7 +16,6 @@ "Folders": "Klasörler", "Genres": "Türler", "HeaderAlbumArtists": "Albüm Sanatçıları", - "HeaderCameraUploads": "Kamera Yüklemeleri", "HeaderContinueWatching": "İzlemeye Devam Et", "HeaderFavoriteAlbums": "Favori Albümler", "HeaderFavoriteArtists": "Favori Sanatçılar", diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index e673465a42..06cc5f633e 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -16,7 +16,6 @@ "HeaderFavoriteArtists": "Улюблені виконавці", "HeaderFavoriteAlbums": "Улюблені альбоми", "HeaderContinueWatching": "Продовжити перегляд", - "HeaderCameraUploads": "Завантажено з камери", "HeaderAlbumArtists": "Виконавці альбому", "Genres": "Жанри", "Folders": "Каталоги", diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json index 9a5874e295..fa7b2d4d0c 100644 --- a/Emby.Server.Implementations/Localization/Core/ur_PK.json +++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json @@ -105,7 +105,6 @@ "Inherit": "وراثت میں", "HomeVideos": "ہوم ویڈیو", "HeaderRecordingGroups": "ریکارڈنگ گروپس", - "HeaderCameraUploads": "کیمرہ اپلوڈز", "FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}", "DeviceOnlineWithName": "{0} متصل ھو چکا ھے", "DeviceOfflineWithName": "{0} منقطع ھو چکا ھے", diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index 2392c83479..ac74deff82 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -103,7 +103,6 @@ "HeaderFavoriteEpisodes": "Tập Phim Yêu Thích", "HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích", "HeaderFavoriteAlbums": "Album Ưa Thích", - "HeaderCameraUploads": "Máy Ảnh Tải Lên", "FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}", "DeviceOnlineWithName": "{0} đã kết nối", "DeviceOfflineWithName": "{0} đã ngắt kết nối", diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 6b563a9b13..53a902de28 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -16,7 +16,6 @@ "Folders": "文件夹", "Genres": "风格", "HeaderAlbumArtists": "专辑作家", - "HeaderCameraUploads": "相机上传", "HeaderContinueWatching": "继续观影", "HeaderFavoriteAlbums": "收藏的专辑", "HeaderFavoriteArtists": "最爱的艺术家", diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 1ac62baca9..435e294ef2 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -16,7 +16,6 @@ "Folders": "檔案夾", "Genres": "風格", "HeaderAlbumArtists": "專輯藝人", - "HeaderCameraUploads": "相機上載", "HeaderContinueWatching": "繼續觀看", "HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteArtists": "最愛的藝人", diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index 7b6540c3e3..30f7266300 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -16,7 +16,6 @@ "Folders": "資料夾", "Genres": "風格", "HeaderAlbumArtists": "專輯演出者", - "HeaderCameraUploads": "相機上傳", "HeaderContinueWatching": "繼續觀賞", "HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteArtists": "最愛演出者", From 5ee6f4920497ec9f4d6c305e9d0828d21074a773 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 1 Oct 2020 08:10:47 -0600 Subject: [PATCH 041/203] Manually register models used in websocket messages. --- .../ApiServiceCollectionExtensions.cs | 1 + .../Filters/WebsocketModelFilter.cs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 Jellyfin.Server/Filters/WebsocketModelFilter.cs diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 5bcf6d5f07..e180d0cd7a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -260,6 +260,7 @@ namespace Jellyfin.Server.Extensions c.AddSwaggerTypeMappings(); c.OperationFilter(); + c.DocumentFilter(); }); } diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs new file mode 100644 index 0000000000..90fca5583c --- /dev/null +++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs @@ -0,0 +1,31 @@ +using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.SyncPlay; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters +{ + /// + /// Add models used in websocket messaging. + /// + public class WebsocketModelFilter : IDocumentFilter + { + /// + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + context.SchemaGenerator.GenerateSchema(typeof(LibraryUpdateInfo), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(IPlugin), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(PlayRequest), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(TimerEventInfo), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(SendCommand), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); + + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + } + } +} From 4a3e0062f9e4c353a79f5cba74a0499aa68565c8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 1 Oct 2020 09:39:57 -0600 Subject: [PATCH 042/203] Add missing PlaystateRequest and remove additional GroupUpdate types --- Jellyfin.Server/Filters/WebsocketModelFilter.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs index 90fca5583c..81dfdfba7e 100644 --- a/Jellyfin.Server/Filters/WebsocketModelFilter.cs +++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs @@ -19,13 +19,12 @@ namespace Jellyfin.Server.Filters context.SchemaGenerator.GenerateSchema(typeof(LibraryUpdateInfo), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(IPlugin), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(PlayRequest), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(PlaystateRequest), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(TimerEventInfo), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(SendCommand), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<>), context.SchemaRepository); } } } From 2b75af987395c242b69f57b636121302eadaee30 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 1 Oct 2020 10:40:58 -0600 Subject: [PATCH 043/203] set type of GroupUpdate --- Jellyfin.Server/Filters/WebsocketModelFilter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs index 81dfdfba7e..2488028576 100644 --- a/Jellyfin.Server/Filters/WebsocketModelFilter.cs +++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Server.Filters context.SchemaGenerator.GenerateSchema(typeof(SendCommand), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<>), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); } } } From d91a4f0c6d81177b03f0543056456058604e0ce1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 1 Oct 2020 20:25:40 +0100 Subject: [PATCH 044/203] Update ApplicationHost.cs --- .../ApplicationHost.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7a46fdf2e7..b4f7319f8e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -819,38 +819,6 @@ namespace Emby.Server.Implementations { try { - if (plugin is IPluginAssembly assemblyPlugin) - { - var assembly = plugin.GetType().Assembly; - var assemblyName = assembly.GetName(); - var assemblyFilePath = assembly.Location; - - var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - - assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); - - try - { - var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); - if (idAttributes.Length > 0) - { - var attribute = (GuidAttribute)idAttributes[0]; - var assemblyId = new Guid(attribute.Value); - - assemblyPlugin.SetId(assemblyId); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName); - } - } - - if (plugin is IHasPluginConfiguration hasPluginConfiguration) - { - hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); - } - plugin.RegisterServices(ServiceCollection); } catch (Exception ex) From a69731b5e32d63042d18188f90dec800b3b1a2b9 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 1 Oct 2020 20:30:12 +0100 Subject: [PATCH 045/203] Update BasePlugin.cs Moved initialisation ApplicationHost.cs /LoadPlugin() --- MediaBrowser.Common/Plugins/BasePlugin.cs | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 4b2918d085..8d9917cd63 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -140,6 +140,37 @@ namespace MediaBrowser.Common.Plugins { ApplicationPaths = applicationPaths; XmlSerializer = xmlSerializer; + if (this is IPluginAssembly assemblyPlugin) + { + var assembly = GetType().Assembly; + var assemblyName = assembly.GetName(); + var assemblyFilePath = assembly.Location; + + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + + assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); + + try + { + var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); + if (idAttributes.Length > 0) + { + var attribute = (GuidAttribute)idAttributes[0]; + var assemblyId = new Guid(attribute.Value); + + assemblyPlugin.SetId(assemblyId); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName); + } + } + + if (this is IHasPluginConfiguration hasPluginConfiguration) + { + hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); + } } /// From dff2674b27da65c0ff7a82575df77be856985b96 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 1 Oct 2020 20:42:48 +0100 Subject: [PATCH 046/203] Update BasePlugin.cs --- MediaBrowser.Common/Plugins/BasePlugin.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 8d9917cd63..8545fd5dcf 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Reflection; +using System.Runtime.InteropServices; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; @@ -150,20 +151,13 @@ namespace MediaBrowser.Common.Plugins assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); - try + var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); + if (idAttributes.Length > 0) { - var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); - if (idAttributes.Length > 0) - { - var attribute = (GuidAttribute)idAttributes[0]; - var assemblyId = new Guid(attribute.Value); + var attribute = (GuidAttribute)idAttributes[0]; + var assemblyId = new Guid(attribute.Value); - assemblyPlugin.SetId(assemblyId); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName); + assemblyPlugin.SetId(assemblyId); } } From 754e859f6e13c0fad0ed91d694b7fca163f58ce1 Mon Sep 17 00:00:00 2001 From: Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Fri, 2 Oct 2020 12:05:39 -0500 Subject: [PATCH 047/203] Convert strings to ImageFormat --- Jellyfin.Api/Controllers/ImageController.cs | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 7afec1219e..6f925dec58 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -364,7 +364,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] string? tag, [FromQuery] bool? cropWhitespace, - [FromQuery] string? format, + [FromQuery] ImageFormat? format, [FromQuery] bool? addPlayedIndicator, [FromQuery] double? percentPlayed, [FromQuery] int? unplayedCount, @@ -443,7 +443,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromRoute, Required] string tag, [FromQuery] bool? cropWhitespace, - [FromRoute, Required] string format, + [FromRoute, Required] ImageFormat format, [FromQuery] bool? addPlayedIndicator, [FromRoute, Required] double percentPlayed, [FromRoute, Required] int unplayedCount, @@ -516,7 +516,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromQuery] string tag, - [FromQuery] string format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -595,7 +595,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromQuery] string tag, - [FromQuery] string format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -674,7 +674,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromQuery] string tag, - [FromQuery] string format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -753,7 +753,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromQuery] string tag, - [FromQuery] string format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -832,7 +832,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromRoute, Required] string tag, - [FromRoute, Required] string format, + [FromRoute, Required] ImageFormat format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -911,7 +911,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromRoute, Required] ImageType imageType, [FromQuery] string? tag, - [FromQuery] string? format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -1038,7 +1038,7 @@ namespace Jellyfin.Api.Controllers ImageType imageType, int? imageIndex, string? tag, - string? format, + ImageFormat? format, int? maxWidth, int? maxHeight, double? percentPlayed, @@ -1128,12 +1128,11 @@ namespace Jellyfin.Api.Controllers isHeadRequest).ConfigureAwait(false); } - private ImageFormat[] GetOutputFormats(string? format) + private ImageFormat[] GetOutputFormats(ImageFormat? format) { - if (!string.IsNullOrWhiteSpace(format) - && Enum.TryParse(format, true, out ImageFormat parsedFormat)) + if (format.HasValue) { - return new[] { parsedFormat }; + return new[] { format.Value }; } return GetClientSupportedFormats(); @@ -1157,7 +1156,7 @@ namespace Jellyfin.Api.Controllers var acceptParam = Request.Query[HeaderNames.Accept]; - var supportsWebP = SupportsFormat(supportedFormats, acceptParam, "webp", false); + var supportsWebP = SupportsFormat(supportedFormats, acceptParam, ImageFormat.Webp, false); if (!supportsWebP) { @@ -1179,7 +1178,7 @@ namespace Jellyfin.Api.Controllers formats.Add(ImageFormat.Jpg); formats.Add(ImageFormat.Png); - if (SupportsFormat(supportedFormats, acceptParam, "gif", true)) + if (SupportsFormat(supportedFormats, acceptParam, ImageFormat.Gif, true)) { formats.Add(ImageFormat.Gif); } @@ -1187,9 +1186,10 @@ namespace Jellyfin.Api.Controllers return formats.ToArray(); } - private bool SupportsFormat(IReadOnlyCollection requestAcceptTypes, string acceptParam, string format, bool acceptAll) + private bool SupportsFormat(IReadOnlyCollection requestAcceptTypes, string acceptParam, ImageFormat format, bool acceptAll) { - var mimeType = "image/" + format; + var normalized = format.ToString().ToLowerInvariant(); + var mimeType = "image/" + normalized; if (requestAcceptTypes.Contains(mimeType)) { @@ -1201,7 +1201,7 @@ namespace Jellyfin.Api.Controllers return true; } - return string.Equals(acceptParam, format, StringComparison.OrdinalIgnoreCase); + return string.Equals(acceptParam, normalized, StringComparison.OrdinalIgnoreCase); } private async Task GetImageResult( From 04cdc89a5c9f92c70078520eb5c63fd2606f2a22 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Fri, 2 Oct 2020 17:14:57 -0700 Subject: [PATCH 048/203] Make MusicBrainzAlbumProvider thread safe --- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index abfa1c6e71..2d37422d04 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -46,6 +46,7 @@ namespace MediaBrowser.Providers.Music private readonly string _musicBrainzBaseUrl; + private SemaphoreSlim _apiRequestLock = new SemaphoreSlim(1, 1); private Stopwatch _stopWatchMusicBrainz = new Stopwatch(); public MusicBrainzAlbumProvider( @@ -742,45 +743,55 @@ namespace MediaBrowser.Providers.Music /// internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { - using var options = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url); - - // MusicBrainz request a contact email address is supplied, as comment, in user agent field: - // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent - options.Headers.UserAgent.ParseAdd(string.Format( - CultureInfo.InvariantCulture, - "{0} ( {1} )", - _appHost.ApplicationUserAgent, - _appHost.ApplicationUserAgentAddress)); - HttpResponseMessage response; var attempts = 0u; + var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url; - do - { - attempts++; + await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false); - if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) + try + { + do { - // MusicBrainz is extremely adamant about limiting to one request per second - var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; - await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); - } + attempts++; - // Write time since last request to debug log as evidence we're meeting rate limit - // requirement, before resetting stopwatch back to zero. - _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); - _stopWatchMusicBrainz.Restart(); + if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) + { + // MusicBrainz is extremely adamant about limiting to one request per second + var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; + await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); + } - response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options).ConfigureAwait(false); + // Write time since last request to debug log as evidence we're meeting rate limit + // requirement, before resetting stopwatch back to zero. + _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); + _stopWatchMusicBrainz.Restart(); - // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) + using var request = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url); + + // MusicBrainz request a contact email address is supplied, as comment, in user agent field: + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent + request.Headers.UserAgent.ParseAdd(string.Format( + CultureInfo.InvariantCulture, + "{0} ( {1} )", + _appHost.ApplicationUserAgent, + _appHost.ApplicationUserAgentAddress)); + + response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(request).ConfigureAwait(false); + + // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) + } + while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); + } + finally + { + _apiRequestLock.Release(); } - while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); // Log error if unable to query MB database due to throttling if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) { - _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.RequestUri); + _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); } return response; From db2e667936a3c982ad28fb383efd427f24e4e37d Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Fri, 2 Oct 2020 17:19:35 -0700 Subject: [PATCH 049/203] expand try finally --- .../Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 2d37422d04..5fe9ef7fa6 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -782,18 +782,18 @@ namespace MediaBrowser.Providers.Music // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) } while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); + + // Log error if unable to query MB database due to throttling + if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) + { + _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); + } } finally { _apiRequestLock.Release(); } - // Log error if unable to query MB database due to throttling - if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) - { - _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); - } - return response; } From 7841378506a04dd02d8e8459a274e740ed5da3f5 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Fri, 2 Oct 2020 17:27:43 -0700 Subject: [PATCH 050/203] cleaner --- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 5fe9ef7fa6..bf3088aa83 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -743,14 +743,14 @@ namespace MediaBrowser.Providers.Music /// internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { - HttpResponseMessage response; - var attempts = 0u; - var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url; - await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { + HttpResponseMessage response; + var attempts = 0u; + var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url; + do { attempts++; @@ -767,7 +767,7 @@ namespace MediaBrowser.Providers.Music _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); _stopWatchMusicBrainz.Restart(); - using var request = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url); + using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl); // MusicBrainz request a contact email address is supplied, as comment, in user agent field: // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent @@ -788,13 +788,13 @@ namespace MediaBrowser.Providers.Music { _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); } + + return response; } finally { _apiRequestLock.Release(); } - - return response; } /// From 2124bc2e1894e5a09e5843cfcf19ab189e70eac1 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sat, 3 Oct 2020 16:04:39 +0800 Subject: [PATCH 051/203] enhance workload when tone mapping with AMF zscale filter is required. --- .../MediaEncoding/EncodingHelper.cs | 78 +++++++++++-------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 790b26f699..4a55840d52 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -859,29 +859,44 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { - switch (encodingOptions.EncoderPreset) + var videoStream = state.VideoStream; + var isColorDepth10 = IsColorDepth10(state); + + if (isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && encodingOptions.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) { - case "veryslow": - case "slow": - case "slower": - param += "-quality quality"; - break; + // Enhance quality and workload when tone mapping with AMF + param += "-quality quality -preanalysis true"; + } + else + { + switch (encodingOptions.EncoderPreset) + { + case "veryslow": + case "slow": + case "slower": + param += "-quality quality"; + break; - case "medium": - param += "-quality balanced"; - break; + case "medium": + param += "-quality balanced"; + break; - case "fast": - case "faster": - case "veryfast": - case "superfast": - case "ultrafast": - param += "-quality speed"; - break; + case "fast": + case "faster": + case "veryfast": + case "superfast": + case "ultrafast": + param += "-quality speed"; + break; - default: - param += "-quality speed"; - break; + default: + param += "-quality speed"; + break; + } } } else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm @@ -2123,19 +2138,18 @@ namespace MediaBrowser.Controller.MediaEncoding if (isSwDecoder || isD3d11vaDecoder) { isScalingInAdvance = true; - // Add scaling filter before tonemapping filter for performance. - filters.AddRange( - GetScalingFilters( - state, - inputWidth, - inputHeight, - threeDFormat, - videoDecoder, - outputVideoCodec, - request.Width, - request.Height, - request.MaxWidth, - request.MaxHeight)); + // Add zscale filter before tone mapping filter for performance. + var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); + if (width.HasValue && height.HasValue) + { + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "zscale=s={0}x{1}", + width.Value, + height.Value)); + } + // Convert to hardware pixel format p010 when using SW decoder. filters.Add("format=p010"); } From 9fbf725a6de88b8381f3d1607a8c722a59b92fbd Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sat, 3 Oct 2020 17:53:10 +0800 Subject: [PATCH 052/203] Enhance workload when tone mapping on some APUs --- .../MediaEncoding/EncodingHelper.cs | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 4a55840d52..0125e909fe 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -859,6 +859,31 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { + switch (encodingOptions.EncoderPreset) + { + case "veryslow": + case "slow": + case "slower": + param += "-quality quality"; + break; + + case "medium": + param += "-quality balanced"; + break; + + case "fast": + case "faster": + case "veryfast": + case "superfast": + case "ultrafast": + param += "-quality speed"; + break; + + default: + param += "-quality speed"; + break; + } + var videoStream = state.VideoStream; var isColorDepth10 = IsColorDepth10(state); @@ -868,35 +893,8 @@ namespace MediaBrowser.Controller.MediaEncoding && !string.IsNullOrEmpty(videoStream.VideoRange) && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) { - // Enhance quality and workload when tone mapping with AMF - param += "-quality quality -preanalysis true"; - } - else - { - switch (encodingOptions.EncoderPreset) - { - case "veryslow": - case "slow": - case "slower": - param += "-quality quality"; - break; - - case "medium": - param += "-quality balanced"; - break; - - case "fast": - case "faster": - case "veryfast": - case "superfast": - case "ultrafast": - param += "-quality speed"; - break; - - default: - param += "-quality speed"; - break; - } + // Enhance workload when tone mapping with AMF on some APUs + param += " -preanalysis true"; } } else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm From 7cec65b97390dd9fc5fe75a81eb69d61d48329c5 Mon Sep 17 00:00:00 2001 From: Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Sat, 3 Oct 2020 14:35:57 -0500 Subject: [PATCH 053/203] Update documentation --- Jellyfin.Api/Controllers/ImageController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 6f925dec58..05efe23553 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -333,7 +333,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. /// Optional. Supply the cache tag from the item object to receive strong caching headers. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. - /// Determines the output format of the image - original,gif,jpg,png. + /// Optional. The of the returned image. /// Optional. Add a played indicator. /// Optional. Percent to render for the percent played overlay. /// Optional. Unplayed count overlay to render. From 4d7e7d6331243bd339464bd6010569c6c308088b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 3 Oct 2020 21:14:25 -0400 Subject: [PATCH 054/203] Rewrite activity log backend to use a query class. --- .../Controllers/ActivityLogController.cs | 20 +++++----- Jellyfin.Data/Queries/ActivityLogQuery.cs | 30 +++++++++++++++ .../Activity/ActivityManager.cs | 37 ++++++++----------- .../Activity/IActivityManager.cs | 9 +---- 4 files changed, 58 insertions(+), 38 deletions(-) create mode 100644 Jellyfin.Data/Queries/ActivityLogQuery.cs diff --git a/Jellyfin.Api/Controllers/ActivityLogController.cs b/Jellyfin.Api/Controllers/ActivityLogController.cs index a07cea9c0c..b429cebecb 100644 --- a/Jellyfin.Api/Controllers/ActivityLogController.cs +++ b/Jellyfin.Api/Controllers/ActivityLogController.cs @@ -1,7 +1,7 @@ using System; -using System.Linq; +using System.Threading.Tasks; using Jellyfin.Api.Constants; -using Jellyfin.Data.Entities; +using Jellyfin.Data.Queries; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; @@ -39,19 +39,19 @@ namespace Jellyfin.Api.Controllers /// A containing the log entries. [HttpGet("Entries")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetLogEntries( + public async Task>> GetLogEntries( [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] DateTime? minDate, [FromQuery] bool? hasUserId) { - var filterFunc = new Func, IQueryable>( - entries => entries.Where(entry => entry.DateCreated >= minDate - && (!hasUserId.HasValue || (hasUserId.Value - ? entry.UserId != Guid.Empty - : entry.UserId == Guid.Empty)))); - - return _activityManager.GetPagedResult(filterFunc, startIndex, limit); + return await _activityManager.GetPagedResultAsync(new ActivityLogQuery + { + StartIndex = startIndex, + Limit = limit, + MinDate = minDate, + HasUserId = hasUserId + }).ConfigureAwait(false); } } } diff --git a/Jellyfin.Data/Queries/ActivityLogQuery.cs b/Jellyfin.Data/Queries/ActivityLogQuery.cs new file mode 100644 index 0000000000..92919d3a51 --- /dev/null +++ b/Jellyfin.Data/Queries/ActivityLogQuery.cs @@ -0,0 +1,30 @@ +using System; + +namespace Jellyfin.Data.Queries +{ + /// + /// A class representing a query to the activity logs. + /// + public class ActivityLogQuery + { + /// + /// Gets or sets the index to start at. + /// + public int? StartIndex { get; set; } + + /// + /// Gets or sets the maximum number of items to include. + /// + public int? Limit { get; set; } + + /// + /// Gets or sets a value indicating whether to take entries with a user id. + /// + public bool? HasUserId { get; set; } + + /// + /// Gets or sets the minimum date to query for. + /// + public DateTime? MinDate { get; set; } + } +} diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index abdd290d45..695e2fbd84 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -3,8 +3,10 @@ using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Querying; +using Microsoft.EntityFrameworkCore; namespace Jellyfin.Server.Implementations.Activity { @@ -39,41 +41,34 @@ namespace Jellyfin.Server.Implementations.Activity } /// - public QueryResult GetPagedResult( - Func, IQueryable> func, - int? startIndex, - int? limit) + public async Task> GetPagedResultAsync(ActivityLogQuery query) { - using var dbContext = _provider.CreateContext(); + await using var dbContext = _provider.CreateContext(); - var query = func(dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated)); + IQueryable entries = dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated); - if (startIndex.HasValue) + if (query.MinDate.HasValue) { - query = query.Skip(startIndex.Value); + entries = entries.Where(entry => entry.DateCreated >= query.MinDate); } - if (limit.HasValue) + if (query.HasUserId.HasValue) { - query = query.Take(limit.Value); + entries = entries.Where(entry => entry.UserId != Guid.Empty == query.HasUserId.Value ); } - // This converts the objects from the new database model to the old for compatibility with the existing API. - var list = query.Select(ConvertToOldModel).ToList(); - return new QueryResult { - Items = list, - TotalRecordCount = func(dbContext.ActivityLogs).Count() + Items = await entries.Skip(query.StartIndex ?? 0) + .Take(query.Limit ?? 100) + .Select(ConvertToOldModel) + .AsQueryable() + .ToListAsync() + .ConfigureAwait(false), + TotalRecordCount = await entries.CountAsync().ConfigureAwait(false) }; } - /// - public QueryResult GetPagedResult(int? startIndex, int? limit) - { - return GetPagedResult(logs => logs, startIndex, limit); - } - private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) { return new ActivityLogEntry diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index d5344494e8..3e4ea208ed 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -1,10 +1,10 @@ #pragma warning disable CS1591 using System; -using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Model.Querying; namespace MediaBrowser.Model.Activity @@ -15,11 +15,6 @@ namespace MediaBrowser.Model.Activity Task CreateAsync(ActivityLog entry); - QueryResult GetPagedResult(int? startIndex, int? limit); - - QueryResult GetPagedResult( - Func, IQueryable> func, - int? startIndex, - int? limit); + Task> GetPagedResultAsync(ActivityLogQuery query); } } From d4a492ef93f6b663fd4a4f7710613f06863f401f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 5 Oct 2020 22:51:52 -0400 Subject: [PATCH 055/203] Fix activity log query. --- .../Activity/ActivityManager.cs | 9 ++++++--- .../Jellyfin.Server.Implementations.csproj | 1 + .../Users/DisplayPreferencesManager.cs | 1 + Jellyfin.Server.Implementations/Users/UserManager.cs | 11 ++++++----- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 695e2fbd84..5926abfe0d 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -45,7 +45,9 @@ namespace Jellyfin.Server.Implementations.Activity { await using var dbContext = _provider.CreateContext(); - IQueryable entries = dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated); + IQueryable entries = dbContext.ActivityLogs + .AsQueryable() + .OrderByDescending(entry => entry.DateCreated); if (query.MinDate.HasValue) { @@ -59,10 +61,11 @@ namespace Jellyfin.Server.Implementations.Activity return new QueryResult { - Items = await entries.Skip(query.StartIndex ?? 0) + Items = await entries + .Skip(query.StartIndex ?? 0) .Take(query.Limit ?? 100) + .AsAsyncEnumerable() .Select(ConvertToOldModel) - .AsQueryable() .ToListAsync() .ConfigureAwait(false), TotalRecordCount = await entries.CountAsync().ConfigureAwait(false) diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 4e79dd8d6c..17ba092588 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -24,6 +24,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index 46f1c618f2..76f9433854 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -61,6 +61,7 @@ namespace Jellyfin.Server.Implementations.Users public IList ListItemDisplayPreferences(Guid userId, string client) { return _dbContext.ItemDisplayPreferences + .AsQueryable() .Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client)) .ToList(); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 8f04baa089..8f6a0496ab 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -108,6 +108,7 @@ namespace Jellyfin.Server.Implementations.Users { using var dbContext = _dbProvider.CreateContext(); return dbContext.Users + .AsQueryable() .Select(user => user.Id) .ToList(); } @@ -200,8 +201,8 @@ namespace Jellyfin.Server.Implementations.Users internal async Task CreateUserInternalAsync(string name, JellyfinDb dbContext) { // TODO: Remove after user item data is migrated. - var max = await dbContext.Users.AnyAsync().ConfigureAwait(false) - ? await dbContext.Users.Select(u => u.InternalId).MaxAsync().ConfigureAwait(false) + var max = await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false) + ? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false) : 0; return new User( @@ -221,7 +222,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); } - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); var newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false); @@ -587,9 +588,9 @@ namespace Jellyfin.Server.Implementations.Users public async Task InitializeAsync() { // TODO: Refactor the startup wizard so that it doesn't require a user to already exist. - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); - if (await dbContext.Users.AnyAsync().ConfigureAwait(false)) + if (await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false)) { return; } From 3d69bcd19090a3e467594375db3ee25f39e3def9 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 6 Oct 2020 08:14:56 +0200 Subject: [PATCH 056/203] Fix playbackstart not triggering in the new eventmanager --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1ab20da22..607b322f2e 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -666,7 +666,7 @@ namespace Emby.Server.Implementations.Session } } - var eventArgs = new PlaybackProgressEventArgs + var eventArgs = new PlaybackStartEventArgs { Item = libraryItem, Users = users, From 38cb8fee8a91c96f37199c64c7ef9414f7466112 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 6 Oct 2020 14:44:07 +0200 Subject: [PATCH 057/203] Fix IWebSocketListener service registration --- .../HttpServer/WebSocketManager.cs | 17 +++++------------ Jellyfin.Server/CoreAppHost.cs | 11 +++++++++++ Jellyfin.Server/Program.cs | 2 +- .../Net/IWebSocketManager.cs | 6 ------ 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 89c1b7ea08..71ece80a75 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net.WebSockets; using System.Threading.Tasks; using Jellyfin.Data.Events; @@ -14,16 +13,18 @@ namespace Emby.Server.Implementations.HttpServer { public class WebSocketManager : IWebSocketManager { + private readonly Lazy> _webSocketListeners; private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private IWebSocketListener[] _webSocketListeners = Array.Empty(); private bool _disposed = false; public WebSocketManager( + Lazy> webSocketListeners, ILogger logger, ILoggerFactory loggerFactory) { + _webSocketListeners = webSocketListeners; _logger = logger; _loggerFactory = loggerFactory; } @@ -68,15 +69,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// Adds the rest handlers. - /// - /// The web socket listeners. - public void Init(IEnumerable listeners) - { - _webSocketListeners = listeners.ToArray(); - } - /// /// Processes the web socket message received. /// @@ -90,7 +82,8 @@ namespace Emby.Server.Implementations.HttpServer IEnumerable GetTasks() { - foreach (var x in _webSocketListeners) + var listeners = _webSocketListeners.Value; + foreach (var x in listeners) { yield return x.ProcessMessageAsync(result); } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 8d569a779a..c44736447f 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -4,6 +4,8 @@ using System.IO; using System.Reflection; using Emby.Drawing; using Emby.Server.Implementations; +using Emby.Server.Implementations.Session; +using Jellyfin.Api.WebSocketListeners; using Jellyfin.Drawing.Skia; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; @@ -14,6 +16,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; using MediaBrowser.Model.IO; using Microsoft.EntityFrameworkCore; @@ -80,6 +83,14 @@ namespace Jellyfin.Server ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); + ServiceCollection.AddScoped(); + ServiceCollection.AddScoped(); + ServiceCollection.AddScoped(); + ServiceCollection.AddScoped(); + + // TODO fix circular dependency on IWebSocketManager + ServiceCollection.AddScoped(serviceProvider => new Lazy>(serviceProvider.GetRequiredService>)); + base.RegisterServices(); } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index c933d679f4..5573c04395 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -378,7 +378,7 @@ namespace Jellyfin.Server .ConfigureServices(services => { // Merge the external ServiceCollection into ASP.NET DI - services.TryAdd(serviceCollection); + services.Add(serviceCollection); }) .UseStartup(); } diff --git a/MediaBrowser.Controller/Net/IWebSocketManager.cs b/MediaBrowser.Controller/Net/IWebSocketManager.cs index e9f00ae88b..ce74173e70 100644 --- a/MediaBrowser.Controller/Net/IWebSocketManager.cs +++ b/MediaBrowser.Controller/Net/IWebSocketManager.cs @@ -16,12 +16,6 @@ namespace MediaBrowser.Controller.Net /// event EventHandler> WebSocketConnected; - /// - /// Inits this instance. - /// - /// The websocket listeners. - void Init(IEnumerable listeners); - /// /// The HTTP request handler. /// From 6def4b06a8916c831ec2bd38bb066aeac7f75594 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 6 Oct 2020 14:54:17 +0200 Subject: [PATCH 058/203] Minor improvements to tmdb code --- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 14 ++++---- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 32 ++++++++--------- .../Tmdb/TV/TmdbSeasonImageProvider.cs | 13 +++---- .../Tmdb/TV/TmdbSeriesImageProvider.cs | 19 ++++++----- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 34 +++++++++++-------- 5 files changed, 60 insertions(+), 52 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index fe9e483c67..3b7a0b254e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -67,17 +67,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken) .ConfigureAwait(false); - if (episodeResult?.Images?.Stills == null) + var stills = episodeResult?.Images?.Stills; + if (stills == null) { return Enumerable.Empty(); } - var remoteImages = new List(); - - for (var i = 0; i < episodeResult.Images.Stills.Count; i++) + var remoteImages = new RemoteImageInfo[stills.Count]; + for (var i = 0; i < stills.Count; i++) { - var image = episodeResult.Images.Stills[i]; - remoteImages.Add(new RemoteImageInfo + var image = stills[i]; + remoteImages[i] = new RemoteImageInfo { Url = _tmdbClientManager.GetStillUrl(image.FilePath), CommunityRating = image.VoteAverage, @@ -88,7 +88,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV ProviderName = Name, Type = ImageType.Primary, RatingType = RatingType.Score - }); + }; } return remoteImages.OrderByLanguageDescending(language); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 55f1ba7dcc..93998a1102 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -40,30 +40,29 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return Enumerable.Empty(); } - var metadataResult = await GetMetadata(searchInfo, cancellationToken); + var metadataResult = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false); if (!metadataResult.HasMetadata) { return Enumerable.Empty(); } - var list = new List(); - var item = metadataResult.Item; - list.Add(new RemoteSearchResult + return new[] { - IndexNumber = item.IndexNumber, - Name = item.Name, - ParentIndexNumber = item.ParentIndexNumber, - PremiereDate = item.PremiereDate, - ProductionYear = item.ProductionYear, - ProviderIds = item.ProviderIds, - SearchProviderName = Name, - IndexNumberEnd = item.IndexNumberEnd - }); - - return list; + new RemoteSearchResult + { + IndexNumber = item.IndexNumber, + Name = item.Name, + ParentIndexNumber = item.ParentIndexNumber, + PremiereDate = item.PremiereDate, + ProductionYear = item.ProductionYear, + ProviderIds = item.ProviderIds, + SearchProviderName = Name, + IndexNumberEnd = item.IndexNumberEnd + } + }; } public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) @@ -137,8 +136,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { if (TmdbUtils.IsTrailerType(video)) { - var videoUrl = string.Format(CultureInfo.InvariantCulture, "http://www.youtube.com/watch?v={0}", video.Key); - item.AddTrailerUrl(videoUrl); + item.AddTrailerUrl("https://www.youtube.com/watch?v=" + video.Key); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index 0feaa5732a..f4ed480aef 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -56,16 +56,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken) .ConfigureAwait(false); - if (seasonResult?.Images?.Posters == null) + var posters = seasonResult?.Images?.Posters; + if (posters == null) { return Enumerable.Empty(); } - var remoteImages = new List(); - for (var i = 0; i < seasonResult.Images.Posters.Count; i++) + var remoteImages = new RemoteImageInfo[posters.Count]; + for (var i = 0; i < posters.Count; i++) { - var image = seasonResult.Images.Posters[i]; - remoteImages.Add(new RemoteImageInfo + var image = posters[i]; + remoteImages[i] = new RemoteImageInfo { Url = _tmdbClientManager.GetPosterUrl(image.FilePath), CommunityRating = image.VoteAverage, @@ -76,7 +77,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV ProviderName = Name, Type = ImageType.Primary, RatingType = RatingType.Score - }); + }; } return remoteImages.OrderByLanguageDescending(language); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index 2fdec0196b..d0c6b8b886 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -68,12 +68,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return Enumerable.Empty(); } - var remoteImages = new List(); + var posters = series.Images.Posters; + var backdrops = series.Images.Backdrops; - for (var i = 0; i < series.Images.Posters.Count; i++) + var remoteImages = new RemoteImageInfo[posters.Count + backdrops.Count]; + + for (var i = 0; i < posters.Count; i++) { - var poster = series.Images.Posters[i]; - remoteImages.Add(new RemoteImageInfo + var poster = posters[i]; + remoteImages[i] = new RemoteImageInfo { Url = _tmdbClientManager.GetPosterUrl(poster.FilePath), CommunityRating = poster.VoteAverage, @@ -84,13 +87,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV ProviderName = Name, Type = ImageType.Primary, RatingType = RatingType.Score - }); + }; } - for (var i = 0; i < series.Images.Backdrops.Count; i++) + for (var i = 0; i < backdrops.Count; i++) { var backdrop = series.Images.Backdrops[i]; - remoteImages.Add(new RemoteImageInfo + remoteImages[posters.Count + i] = new RemoteImageInfo { Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath), CommunityRating = backdrop.VoteAverage, @@ -100,7 +103,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV ProviderName = Name, Type = ImageType.Backdrop, RatingType = RatingType.Score - }); + }; } return remoteImages.OrderByLanguageDescending(language); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 7944dfe27c..942c85b90d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -66,14 +66,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, cancellationToken) .ConfigureAwait(false); - if (findResult?.TvResults != null) + var tvResults = findResult?.TvResults; + if (tvResults != null) { - var imdbIdResults = new List(); - for (var i = 0; i < findResult.TvResults.Count; i++) + var imdbIdResults = new RemoteSearchResult[tvResults.Count]; + for (var i = 0; i < tvResults.Count; i++) { - var remoteResult = MapSearchTvToRemoteSearchResult(findResult.TvResults[i]); + var remoteResult = MapSearchTvToRemoteSearchResult(tvResults[i]); remoteResult.SetProviderId(MetadataProvider.Imdb, imdbId); - imdbIdResults.Add(remoteResult); + imdbIdResults[i] = remoteResult; } return imdbIdResults; @@ -88,14 +89,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, cancellationToken) .ConfigureAwait(false); - if (findResult?.TvResults != null) + var tvResults = findResult?.TvResults; + if (tvResults != null) { - var tvIdResults = new List(); - for (var i = 0; i < findResult.TvResults.Count; i++) + var tvIdResults = new RemoteSearchResult[tvResults.Count]; + for (var i = 0; i < tvResults.Count; i++) { - var remoteResult = MapSearchTvToRemoteSearchResult(findResult.TvResults[i]); + var remoteResult = MapSearchTvToRemoteSearchResult(tvResults[i]); remoteResult.SetProviderId(MetadataProvider.Tvdb, tvdbId); - tvIdResults.Add(remoteResult); + tvIdResults[i] = remoteResult; } return tvIdResults; @@ -105,10 +107,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken) .ConfigureAwait(false); - var remoteResults = new List(); + var remoteResults = new RemoteSearchResult[tvSearchResults.Count]; for (var i = 0; i < tvSearchResults.Count; i++) { - remoteResults.Add(MapSearchTvToRemoteSearchResult(tvSearchResults[i])); + remoteResults[i] = MapSearchTvToRemoteSearchResult(tvSearchResults[i]); } return remoteResults; @@ -236,7 +238,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV private Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode) { - var series = new Series {Name = seriesResult.Name, OriginalTitle = seriesResult.OriginalName}; + var series = new Series + { + Name = seriesResult.Name, + OriginalTitle = seriesResult.OriginalName + }; series.SetProviderId(MetadataProvider.Tmdb, seriesResult.Id.ToString(CultureInfo.InvariantCulture)); @@ -322,7 +328,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { if (TmdbUtils.IsTrailerType(video)) { - series.AddTrailerUrl($"http://www.youtube.com/watch?v={video.Key}"); + series.AddTrailerUrl("https://www.youtube.com/watch?v=" + video.Key); } } } From 137baab0ac608f96cac9649de7860b3bbdf2b21c Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 6 Oct 2020 20:19:36 +0200 Subject: [PATCH 059/203] Remove websocketmanager from ApplicationHost --- Emby.Server.Implementations/ApplicationHost.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0c8b0339bd..ee0af1025e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -128,7 +128,6 @@ namespace Emby.Server.Implementations private IMediaEncoder _mediaEncoder; private ISessionManager _sessionManager; private IHttpClientFactory _httpClientFactory; - private IWebSocketManager _webSocketManager; private string[] _urlPrefixes; @@ -259,8 +258,8 @@ namespace Emby.Server.Implementations IServiceCollection serviceCollection) { _xmlSerializer = new MyXmlSerializer(); - _jsonSerializer = new JsonSerializer(); - + _jsonSerializer = new JsonSerializer(); + ServiceCollection = serviceCollection; _networkManager = networkManager; @@ -667,7 +666,6 @@ namespace Emby.Server.Implementations _mediaEncoder = Resolve(); _sessionManager = Resolve(); _httpClientFactory = Resolve(); - _webSocketManager = Resolve(); ((AuthenticationRepository)Resolve()).Initialize(); @@ -788,7 +786,6 @@ namespace Emby.Server.Implementations .ToArray(); _urlPrefixes = GetUrlPrefixes().ToArray(); - _webSocketManager.Init(GetExports()); Resolve().AddParts( GetExports(), @@ -1090,7 +1087,7 @@ namespace Emby.Server.Implementations { // No metafile, so lets see if the folder is versioned. metafile = dir.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)[^1]; - + int versionIndex = dir.LastIndexOf('_'); if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version ver)) { @@ -1099,9 +1096,9 @@ namespace Emby.Server.Implementations } else { - // Un-versioned folder - Add it under the path name and version 0.0.0.1. + // Un-versioned folder - Add it under the path name and version 0.0.0.1. versions.Add((new Version(0, 0, 0, 1), metafile, dir)); - } + } } } catch From 508b7c0a4e24d52fa8c10a29f6fb5c1cf77a65d2 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 7 Oct 2020 17:16:03 -0600 Subject: [PATCH 060/203] Add missing general commands --- MediaBrowser.Model/Session/GeneralCommandType.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Session/GeneralCommandType.cs b/MediaBrowser.Model/Session/GeneralCommandType.cs index 5a9042d5f9..dce33210bb 100644 --- a/MediaBrowser.Model/Session/GeneralCommandType.cs +++ b/MediaBrowser.Model/Session/GeneralCommandType.cs @@ -43,6 +43,9 @@ namespace MediaBrowser.Model.Session Guide = 32, ToggleStats = 33, PlayMediaSource = 34, - PlayTrailers = 35 + PlayTrailers = 35, + SetShuffleQueue = 36, + Playstate = 37, + PlayNext = 38 } } From e9f17f3cab621929eacb6680a48246a4033d727d Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 7 Oct 2020 17:26:02 -0600 Subject: [PATCH 061/203] Add missing general commands for Kodi --- MediaBrowser.Model/Session/GeneralCommandType.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Session/GeneralCommandType.cs b/MediaBrowser.Model/Session/GeneralCommandType.cs index dce33210bb..62fa436b5e 100644 --- a/MediaBrowser.Model/Session/GeneralCommandType.cs +++ b/MediaBrowser.Model/Session/GeneralCommandType.cs @@ -46,6 +46,8 @@ namespace MediaBrowser.Model.Session PlayTrailers = 35, SetShuffleQueue = 36, Playstate = 37, - PlayNext = 38 + PlayNext = 38, + ToggleOsdMenu = 39, + Play = 40 } } From fe49ae7cb558fe86f23cb11f6f564db59828509f Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 8 Oct 2020 07:21:55 -0600 Subject: [PATCH 062/203] fix casing --- MediaBrowser.Model/Session/GeneralCommandType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Session/GeneralCommandType.cs b/MediaBrowser.Model/Session/GeneralCommandType.cs index 62fa436b5e..c58fa9a6b9 100644 --- a/MediaBrowser.Model/Session/GeneralCommandType.cs +++ b/MediaBrowser.Model/Session/GeneralCommandType.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Model.Session PlayMediaSource = 34, PlayTrailers = 35, SetShuffleQueue = 36, - Playstate = 37, + PlayState = 37, PlayNext = 38, ToggleOsdMenu = 39, Play = 40 From 2a6ea45ee1106a46c9947995d3b4e314ce68c495 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 08:43:59 -0600 Subject: [PATCH 063/203] Run npm install task --- .ci/azure-pipelines-api-client.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index 1321014445..8b03b233fb 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -28,6 +28,12 @@ jobs: inputs: script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar" + - task: Npm@1 + displayName: 'Install npm dependencies' + inputs: + command: install + workingDir: ./apiclient/generated/typescript/axios + # Generate npm api client # Unstable - task: CmdLine@2 From 60c0cc1d1cd1b6ebd58b47021652782ceddf232e Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 08:51:22 -0600 Subject: [PATCH 064/203] Fix feed name and url --- .ci/azure-pipelines-api-client.yml | 2 +- apiclient/templates/typescript/axios/generate.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index 8b03b233fb..35a494cd6b 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -48,7 +48,7 @@ jobs: inputs: command: publish publishRegistry: useFeed - publishFeed: 'unstable@Local' + publishFeed: 'jellyfin/unstable' workingDir: ./apiclient/generated/typescript/axios # Stable diff --git a/apiclient/templates/typescript/axios/generate.sh b/apiclient/templates/typescript/axios/generate.sh index 360fe9f33b..8c4d742825 100644 --- a/apiclient/templates/typescript/axios/generate.sh +++ b/apiclient/templates/typescript/axios/generate.sh @@ -4,7 +4,7 @@ artifactsDirectory="${1}" buildNumber="${2}" if [[ -n ${buildNumber} ]]; then # Unstable build - additionalProperties=",snapshotVersion=-SNAPSHOT.${buildNumber},npmRepository=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable%40Local/npm/registry/" + additionalProperties=",snapshotVersion=-SNAPSHOT.${buildNumber},npmRepository=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/" else # Stable build additionalProperties="" From 5109d558d927a3aa5ebc12ab4c961f0213946197 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 09:52:05 -0600 Subject: [PATCH 065/203] fix npm install order --- .ci/azure-pipelines-api-client.yml | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index 35a494cd6b..fc89b90d4e 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -28,13 +28,7 @@ jobs: inputs: script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar" - - task: Npm@1 - displayName: 'Install npm dependencies' - inputs: - command: install - workingDir: ./apiclient/generated/typescript/axios - -# Generate npm api client +## Generate npm api client # Unstable - task: CmdLine@2 displayName: 'Build unstable typescript axios client' @@ -42,6 +36,22 @@ jobs: inputs: script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory) $(Build.BuildNumber)" +# Stable + - task: CmdLine@2 + displayName: 'Build stable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + inputs: + script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)" + +## Run npm install + - task: Npm@1 + displayName: 'Install npm dependencies' + inputs: + command: install + workingDir: ./apiclient/generated/typescript/axios + +## Publish npm packages +# Unstable - task: Npm@1 displayName: 'Publish unstable typescript axios client' condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') @@ -52,12 +62,6 @@ jobs: workingDir: ./apiclient/generated/typescript/axios # Stable - - task: CmdLine@2 - displayName: 'Build stable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') - inputs: - script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)" - - task: Npm@1 displayName: 'Publish stable typescript axios client' condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') From 17b20aa0c03b17cf41494983115127abc6f7ede1 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 17:03:45 -0600 Subject: [PATCH 066/203] Fix comma delimited array model binder --- Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs index 13469194a0..208566dc8e 100644 --- a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs @@ -18,11 +18,6 @@ namespace Jellyfin.Api.ModelBinders var elementType = bindingContext.ModelType.GetElementType(); var converter = TypeDescriptor.GetConverter(elementType); - if (valueProviderResult == ValueProviderResult.None) - { - return Task.CompletedTask; - } - if (valueProviderResult.Length > 1) { var result = Array.CreateInstance(elementType, valueProviderResult.Length); From 6a3a193dbbe856a22662f495af04aca729495d1f Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 17:10:06 -0600 Subject: [PATCH 067/203] Fix and clean tests --- .../CommaDelimitedArrayModelBinderTests.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs index c801b4a523..89c7d62f79 100644 --- a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs +++ b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs @@ -1,17 +1,13 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Globalization; -using System.Text; using System.Threading.Tasks; using Jellyfin.Api.ModelBinders; -using MediaBrowser.Controller.Entities; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Primitives; using Moq; using Xunit; -using Xunit.Sdk; namespace Jellyfin.Api.Tests.ModelBinders { @@ -21,14 +17,14 @@ namespace Jellyfin.Api.Tests.ModelBinders public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedStringArrayQuery() { var queryParamName = "test"; - var queryParamValues = new string[] { "lol", "xd" }; + var queryParamValues = new[] { "lol", "xd" }; var queryParamString = "lol,xd"; var queryParamType = typeof(string[]); var modelBinder = new CommaDelimitedArrayModelBinder(); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), - new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); @@ -46,14 +42,14 @@ namespace Jellyfin.Api.Tests.ModelBinders public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedIntArrayQuery() { var queryParamName = "test"; - var queryParamValues = new int[] { 42, 0 }; + var queryParamValues = new[] { 42, 0 }; var queryParamString = "42,0"; var queryParamType = typeof(int[]); var modelBinder = new CommaDelimitedArrayModelBinder(); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), - new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); @@ -71,14 +67,14 @@ namespace Jellyfin.Api.Tests.ModelBinders public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery() { var queryParamName = "test"; - var queryParamValues = new TestType[] { TestType.How, TestType.Much }; + var queryParamValues = new[] { TestType.How, TestType.Much }; var queryParamString = "How,Much"; var queryParamType = typeof(TestType[]); var modelBinder = new CommaDelimitedArrayModelBinder(); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), - new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); @@ -96,14 +92,14 @@ namespace Jellyfin.Api.Tests.ModelBinders public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQueryWithDoubleCommas() { var queryParamName = "test"; - var queryParamValues = new TestType[] { TestType.How, TestType.Much }; + var queryParamValues = new[] { TestType.How, TestType.Much }; var queryParamString = "How,,Much"; var queryParamType = typeof(TestType[]); var modelBinder = new CommaDelimitedArrayModelBinder(); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), - new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); @@ -121,7 +117,7 @@ namespace Jellyfin.Api.Tests.ModelBinders public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery() { var queryParamName = "test"; - var queryParamValues = new TestType[] { TestType.How, TestType.Much }; + var queryParamValues = new[] { TestType.How, TestType.Much }; var queryParamString1 = "How"; var queryParamString2 = "Much"; var queryParamType = typeof(TestType[]); @@ -130,9 +126,9 @@ namespace Jellyfin.Api.Tests.ModelBinders var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), - new QueryCollection(new Dictionary() + new QueryCollection(new Dictionary { - { queryParamName, new StringValues(new string[] { queryParamString1, queryParamString2 }) }, + { queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) }, }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); @@ -158,7 +154,7 @@ namespace Jellyfin.Api.Tests.ModelBinders var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), - new QueryCollection(new Dictionary() + new QueryCollection(new Dictionary { { queryParamName, new StringValues(value: null) }, }), @@ -171,7 +167,8 @@ namespace Jellyfin.Api.Tests.ModelBinders await modelBinder.BindModelAsync(bindingContextMock.Object); - Assert.False(bindingContextMock.Object.Result.IsModelSet); + Assert.True(bindingContextMock.Object.Result.IsModelSet); + Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues); } [Fact] @@ -184,7 +181,7 @@ namespace Jellyfin.Api.Tests.ModelBinders var modelBinder = new CommaDelimitedArrayModelBinder(); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), - new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); @@ -201,7 +198,6 @@ namespace Jellyfin.Api.Tests.ModelBinders public async Task BindModelAsync_ThrowsIfCommaDelimitedEnumArrayQueryIsInvalid2() { var queryParamName = "test"; - var queryParamValues = new TestType[] { TestType.How, TestType.Much }; var queryParamString1 = "How"; var queryParamString2 = "😱"; var queryParamType = typeof(TestType[]); @@ -210,9 +206,9 @@ namespace Jellyfin.Api.Tests.ModelBinders var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), - new QueryCollection(new Dictionary() + new QueryCollection(new Dictionary { - { queryParamName, new StringValues(new string[] { queryParamString1, queryParamString2 }) }, + { queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) }, }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); From 67722a2034d9ab4b3d6df82f11c2c12a5bfedf38 Mon Sep 17 00:00:00 2001 From: shakesac Date: Fri, 9 Oct 2020 23:05:24 +0000 Subject: [PATCH 068/203] Translated using Weblate (Portuguese (Portugal)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/ --- Emby.Server.Implementations/Localization/Core/pt-PT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index c1fb65743d..a7c17ecb63 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -26,7 +26,7 @@ "HeaderLiveTV": "TV em Direto", "HeaderNextUp": "A Seguir", "HeaderRecordingGroups": "Grupos de Gravação", - "HomeVideos": "Videos caseiros", + "HomeVideos": "Vídeos Caseiros", "Inherit": "Herdar", "ItemAddedWithName": "{0} foi adicionado à biblioteca", "ItemRemovedWithName": "{0} foi removido da biblioteca", From 69360b749a53bd41087530f7fbe2e0c7798f704b Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 17:35:08 -0600 Subject: [PATCH 069/203] Convert field string to enum. --- Jellyfin.Api/Controllers/ArtistsController.cs | 8 +++--- .../Controllers/ChannelsController.cs | 8 +++--- Jellyfin.Api/Controllers/GenresController.cs | 4 +-- .../Controllers/InstantMixController.cs | 28 +++++++++---------- Jellyfin.Api/Controllers/ItemsController.cs | 4 +-- Jellyfin.Api/Controllers/LibraryController.cs | 4 +-- Jellyfin.Api/Controllers/LiveTvController.cs | 25 +++++++++-------- Jellyfin.Api/Controllers/MoviesController.cs | 2 +- .../Controllers/MusicGenresController.cs | 4 +-- Jellyfin.Api/Controllers/PersonsController.cs | 4 +-- .../Controllers/PlaylistsController.cs | 4 +-- Jellyfin.Api/Controllers/StudiosController.cs | 4 +-- .../Controllers/TrailersController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 14 +++++----- .../Controllers/UserLibraryController.cs | 4 +-- Jellyfin.Api/Controllers/YearsController.cs | 4 +-- Jellyfin.Api/Extensions/DtoExtensions.cs | 26 ++--------------- 17 files changed, 65 insertions(+), 84 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index d38214116c..e9e0160217 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -51,7 +51,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// Optional. Search term. /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. @@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? filters, @@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// Optional. Search term. /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. @@ -295,7 +295,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? filters, diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index 33a969f859..732e24799a 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -107,7 +107,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Sort Order - Ascending,Descending. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Channel items returned. /// /// A representing the request to get the channel items. @@ -123,7 +123,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? sortOrder, [FromQuery] string? filters, [FromQuery] string? sortBy, - [FromQuery] string? fields) + [FromQuery] ItemFields[] fields) { var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) @@ -184,7 +184,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Specify one or more channel id's, comma delimited. /// Latest channel items returned. /// @@ -197,7 +197,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? filters, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? channelIds) { var user = userId.HasValue && !userId.Equals(Guid.Empty) diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index de6aa86c94..aad652a404 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -52,7 +52,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// The search term. /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. @@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? filters, diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 07fed97642..eae5ba843d 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -54,7 +54,7 @@ namespace Jellyfin.Api.Controllers /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. @@ -67,7 +67,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -91,7 +91,7 @@ namespace Jellyfin.Api.Controllers /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. @@ -104,7 +104,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -128,7 +128,7 @@ namespace Jellyfin.Api.Controllers /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. @@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -165,7 +165,7 @@ namespace Jellyfin.Api.Controllers /// The genre name. /// Optional. Filter by user id, and attach user data. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. @@ -178,7 +178,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -201,7 +201,7 @@ namespace Jellyfin.Api.Controllers /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. @@ -214,7 +214,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -238,7 +238,7 @@ namespace Jellyfin.Api.Controllers /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. @@ -251,7 +251,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -275,7 +275,7 @@ namespace Jellyfin.Api.Controllers /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. @@ -288,7 +288,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 652c4689d0..5fd302db9e 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -179,7 +179,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] string? sortOrder, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? filters, @@ -535,7 +535,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? mediaTypes, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 8a872ae133..865b0010d8 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -693,7 +693,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? excludeArtistIds, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] string? fields) + [FromQuery] ItemFields[] fields) { var item = itemId.Equals(Guid.Empty) ? (!userId.Equals(Guid.Empty) @@ -885,7 +885,7 @@ namespace Jellyfin.Api.Controllers string? excludeArtistIds, Guid? userId, int? limit, - string? fields, + ItemFields[] fields, string[] includeItemTypes, bool isMovie) { diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 3557e63047..8c316cfbaf 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Include image information in output. /// Optional. The max number of images to return, per image type. /// "Optional. The image types to include in the output. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include user data. /// Optional. Key to sort by. /// Optional. Sort order. @@ -146,7 +146,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, [FromQuery] string? enableImageTypes, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder, @@ -238,7 +238,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Include image information in output. /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include user data. /// Optional. Filter for movies. /// Optional. Filter for series. @@ -263,7 +263,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, [FromQuery] string? enableImageTypes, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool? isMovie, [FromQuery] bool? isSeries, @@ -295,7 +295,7 @@ namespace Jellyfin.Api.Controllers IsKids = isKids, IsSports = isSports, IsLibraryItem = isLibraryItem, - Fields = RequestHelpers.GetItemFields(fields), + Fields = fields, ImageTypeLimit = imageTypeLimit, EnableImages = enableImages }, dtoOptions); @@ -315,7 +315,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Include image information in output. /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include user data. /// Optional. Return total record count. /// Live tv recordings returned. @@ -350,7 +350,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, [FromQuery] string? enableImageTypes, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { @@ -529,7 +529,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Include user data. /// Optional. Filter by series timer id. /// Optional. Filter by library series id. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Retrieve total record count. /// Live tv epgs returned. /// @@ -564,7 +564,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableUserData, [FromQuery] string? seriesTimerId, [FromQuery] Guid? librarySeriesId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool enableTotalRecordCount = true) { var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -661,8 +661,9 @@ namespace Jellyfin.Api.Controllers } } + var fields = RequestHelpers.GetItemFields(body.Fields); var dtoOptions = new DtoOptions() - .AddItemFields(body.Fields) + .AddItemFields(fields) .AddClientFields(Request) .AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes); return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); @@ -684,7 +685,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// The genres to return guide information for. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. include user data. /// Retrieve total record count. /// Recommended epgs returned. @@ -706,7 +707,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? imageTypeLimit, [FromQuery] string? enableImageTypes, [FromQuery] string? genreIds, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index 7fcfc749de..bf8279e0c7 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetMovieRecommendations( [FromQuery] Guid? userId, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] int categoryLimit = 5, [FromQuery] int itemLimit = 8) { diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 570ae8fdc7..bdcdf503eb 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -52,7 +52,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// The search term. /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. @@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? filters, diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 8bd610dad9..4b0a84df23 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -51,7 +51,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// The search term. /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. @@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? filters, diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 1e95bd2b38..31c66c3269 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers /// User id. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. @@ -147,7 +147,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, Required] Guid userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index cdd5f958e9..886272c49a 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -50,7 +50,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// Optional. Search term. /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. @@ -85,7 +85,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? filters, diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 5157b08ae5..0662a39edc 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] string? sortOrder, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? filters, [FromQuery] bool? isFavorite, diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index d158f6c342..27e18eb38c 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -57,7 +57,7 @@ namespace Jellyfin.Api.Controllers /// The user id of the user to get the next up episodes for. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Filter by series id. /// Optional. Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Include image information in output. @@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? seriesId, [FromQuery] string? parentId, [FromQuery] bool? enableImges, @@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool enableTotalRecordCount = true) { var options = new DtoOptions() - .AddItemFields(fields!) + .AddItemFields(fields) .AddClientFields(Request) .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!); @@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers /// The user id of the user to get the upcoming episodes for. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// Optional. Specify additional fields of information to return in the output. /// Optional. Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Include image information in output. /// Optional. The max number of images to return, per image type. @@ -130,7 +130,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? parentId, [FromQuery] bool? enableImges, [FromQuery] int? imageTypeLimit, @@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetEpisodes( [FromRoute, Required] string seriesId, [FromQuery] Guid? userId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] int? season, [FromQuery] string? seasonId, [FromQuery] bool? isMissing, @@ -319,7 +319,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetSeasons( [FromRoute, Required] string seriesId, [FromQuery] Guid? userId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] bool? isSpecialSeason, [FromQuery] bool? isMissing, [FromQuery] string? adjacentTo, diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 48262f0620..981c98b6aa 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -251,7 +251,7 @@ namespace Jellyfin.Api.Controllers /// /// User id. /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted. /// Filter by items that are played, or not. /// Optional. include image information in output. @@ -267,7 +267,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetLatestMedia( [FromRoute, Required] Guid userId, [FromQuery] Guid? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? includeItemTypes, [FromQuery] bool? isPlayed, [FromQuery] bool? enableImages, diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 4ecf0407bf..707a31f757 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -50,7 +50,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// Sort Order - Ascending,Descending. /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be excluded based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be included based on item type. This allows multiple, comma delimited. /// Optional. Filter by MediaType. Allows multiple, comma delimited. @@ -70,7 +70,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? sortOrder, [FromQuery] string? parentId, - [FromQuery] string? fields, + [FromQuery] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? mediaTypes, diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs index e61e9c29d9..cbe748bcf4 100644 --- a/Jellyfin.Api/Extensions/DtoExtensions.cs +++ b/Jellyfin.Api/Extensions/DtoExtensions.cs @@ -21,31 +21,11 @@ namespace Jellyfin.Api.Extensions /// Legacy order: 1. /// /// DtoOptions object. - /// Comma delimited string of fields. + /// Array of item fields. /// Modified DtoOptions object. - internal static DtoOptions AddItemFields(this DtoOptions dtoOptions, string? fields) + internal static DtoOptions AddItemFields(this DtoOptions dtoOptions, ItemFields[] fields) { - if (string.IsNullOrEmpty(fields)) - { - dtoOptions.Fields = Array.Empty(); - } - else - { - dtoOptions.Fields = fields.Split(',') - .Select(v => - { - if (Enum.TryParse(v, true, out ItemFields value)) - { - return (ItemFields?)value; - } - - return null; - }) - .Where(i => i.HasValue) - .Select(i => i!.Value) - .ToArray(); - } - + dtoOptions.Fields = fields; return dtoOptions; } From 549f2284ad87e78bcf43cc9baec2ce7977769dc4 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 17:41:17 -0600 Subject: [PATCH 070/203] Convert filters string to enum. --- Jellyfin.Api/Controllers/ArtistsController.cs | 12 ++++++------ Jellyfin.Api/Controllers/ChannelsController.cs | 12 ++++++------ Jellyfin.Api/Controllers/GenresController.cs | 6 +++--- Jellyfin.Api/Controllers/ItemsController.cs | 4 ++-- Jellyfin.Api/Controllers/MusicGenresController.cs | 6 +++--- Jellyfin.Api/Controllers/PersonsController.cs | 4 ++-- Jellyfin.Api/Controllers/StudiosController.cs | 6 +++--- Jellyfin.Api/Controllers/TrailersController.cs | 2 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 12 ------------ 9 files changed, 26 insertions(+), 38 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index d38214116c..784ecaeb32 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -54,7 +54,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. /// Optional filter by MediaType. Allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. @@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, @@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers }).Where(i => i != null).Select(i => i!.Id).ToArray(); } - foreach (var filter in RequestHelpers.GetFilters(filters)) + foreach (var filter in filters) { switch (filter) { @@ -263,7 +263,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. /// Optional filter by MediaType. Allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. @@ -298,7 +298,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, @@ -397,7 +397,7 @@ namespace Jellyfin.Api.Controllers }).Where(i => i != null).Select(i => i!.Id).ToArray(); } - foreach (var filter in RequestHelpers.GetFilters(filters)) + foreach (var filter in filters) { switch (filter) { diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index 33a969f859..fda00b8d46 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -105,7 +105,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Optional. Sort Order - Ascending,Descending. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Channel items returned. @@ -121,7 +121,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? sortOrder, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] string? sortBy, [FromQuery] string? fields) { @@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers .AddItemFields(fields) }; - foreach (var filter in RequestHelpers.GetFilters(filters)) + foreach (var filter in filters) { switch (filter) { @@ -183,7 +183,7 @@ namespace Jellyfin.Api.Controllers /// Optional. User Id. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. Specify one or more channel id's, comma delimited. /// Latest channel items returned. @@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] string? fields, [FromQuery] string? channelIds) { @@ -217,7 +217,7 @@ namespace Jellyfin.Api.Controllers .AddItemFields(fields) }; - foreach (var filter in RequestHelpers.GetFilters(filters)) + foreach (var filter in filters) { switch (filter) { diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index de6aa86c94..f4b69b312e 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -55,7 +55,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. /// Optional filter by MediaType. Allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. @@ -90,7 +90,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, @@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers .ToArray(); } - foreach (var filter in RequestHelpers.GetFilters(filters)) + foreach (var filter in filters) { switch (filter) { diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 652c4689d0..1f73bd8582 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -182,7 +182,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? imageTypes, @@ -365,7 +365,7 @@ namespace Jellyfin.Api.Controllers query.CollapseBoxSetItems = false; } - foreach (var filter in RequestHelpers.GetFilters(filters!)) + foreach (var filter in filters) { switch (filter) { diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 570ae8fdc7..d216b7f728 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -55,7 +55,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. /// Optional filter by MediaType. Allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. @@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, @@ -187,7 +187,7 @@ namespace Jellyfin.Api.Controllers .ToArray(); } - foreach (var filter in RequestHelpers.GetFilters(filters)) + foreach (var filter in filters) { switch (filter) { diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 8bd610dad9..bbef1ff9a1 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, @@ -187,7 +187,7 @@ namespace Jellyfin.Api.Controllers .ToArray(); } - foreach (var filter in RequestHelpers.GetFilters(filters)) + foreach (var filter in filters) { switch (filter) { diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index cdd5f958e9..3a8ac1b243 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -53,7 +53,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. /// Optional filter by MediaType. Allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. @@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, @@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers .ToArray(); } - foreach (var filter in RequestHelpers.GetFilters(filters)) + foreach (var filter in filters) { switch (filter) { diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 5157b08ae5..08d9f78fc5 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -146,7 +146,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? parentId, [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, - [FromQuery] string? filters, + [FromQuery] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? imageTypes, diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 8dcf08af56..fb5c283f59 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -56,18 +56,6 @@ namespace Jellyfin.Api.Helpers return result; } - /// - /// Get parsed filters. - /// - /// The filters. - /// Item filters. - public static IEnumerable GetFilters(string? filters) - { - return string.IsNullOrEmpty(filters) - ? Array.Empty() - : filters.Split(',').Select(v => Enum.Parse(v, true)); - } - /// /// Splits a string at a separating character into an array of substrings. /// From ed4b470397c41ba1c86b2f4161874ef741e275c5 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 17:46:39 -0600 Subject: [PATCH 071/203] Convert exclude location type string to enum. --- Jellyfin.Api/Controllers/ItemsController.cs | 9 +++------ Jellyfin.Api/Controllers/TrailersController.cs | 3 ++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 652c4689d0..fd7187cd4f 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -159,7 +159,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isHd, [FromQuery] bool? is4K, [FromQuery] string? locationTypes, - [FromQuery] string? excludeLocationTypes, + [FromQuery] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, @@ -406,12 +406,9 @@ namespace Jellyfin.Api.Controllers } // ExcludeLocationTypes - if (!string.IsNullOrEmpty(excludeLocationTypes)) + if (excludeLocationTypes.Any(t => t == LocationType.Virtual)) { - if (excludeLocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray().Contains(LocationType.Virtual)) - { - query.IsVirtualItem = false; - } + query.IsVirtualItem = false; } if (!string.IsNullOrEmpty(locationTypes)) diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 5157b08ae5..bf2dfcc395 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -1,6 +1,7 @@ using System; using Jellyfin.Api.Constants; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -124,7 +125,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isHd, [FromQuery] bool? is4K, [FromQuery] string? locationTypes, - [FromQuery] string? excludeLocationTypes, + [FromQuery] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, From 27e753ddb4f6eb43ef867bff2cdf757702bffa1c Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 17:52:39 -0600 Subject: [PATCH 072/203] Convert image type string to enum. --- Jellyfin.Api/Controllers/ArtistsController.cs | 5 ++-- Jellyfin.Api/Controllers/GenresController.cs | 3 ++- .../Controllers/InstantMixController.cs | 15 ++++++----- Jellyfin.Api/Controllers/ItemsController.cs | 8 +++--- Jellyfin.Api/Controllers/LiveTvController.cs | 14 +++++----- .../Controllers/MusicGenresController.cs | 3 ++- Jellyfin.Api/Controllers/PersonsController.cs | 3 ++- .../Controllers/PlaylistsController.cs | 3 ++- Jellyfin.Api/Controllers/StudiosController.cs | 3 ++- .../Controllers/TrailersController.cs | 5 ++-- Jellyfin.Api/Controllers/TvShowsController.cs | 9 ++++--- .../Controllers/UserLibraryController.cs | 2 +- Jellyfin.Api/Controllers/YearsController.cs | 3 ++- Jellyfin.Api/Extensions/DtoExtensions.cs | 8 +++--- Jellyfin.Api/Helpers/RequestHelpers.cs | 27 +++++++++++++++++++ 15 files changed, 74 insertions(+), 37 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index d38214116c..9e98811877 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -99,7 +100,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, @@ -308,7 +309,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index de6aa86c94..c065b9b576 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -100,7 +101,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 07fed97642..e6e6b3e70e 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -71,7 +72,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes) + [FromQuery] ImageType[] enableImageTypes) { var item = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -108,7 +109,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes) + [FromQuery] ImageType[] enableImageTypes) { var album = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -145,7 +146,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes) + [FromQuery] ImageType[] enableImageTypes) { var playlist = (Playlist)_libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -182,7 +183,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes) + [FromQuery] ImageType[] enableImageTypes) { var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) @@ -218,7 +219,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes) + [FromQuery] ImageType[] enableImageTypes) { var item = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -255,7 +256,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes) + [FromQuery] ImageType[] enableImageTypes) { var item = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -292,7 +293,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes) + [FromQuery] ImageType[] enableImageTypes) { var item = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 652c4689d0..3411361af1 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -185,7 +185,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, - [FromQuery] string? imageTypes, + [FromQuery] ImageType[] imageTypes, [FromQuery] string? sortBy, [FromQuery] bool? isPlayed, [FromQuery] string? genres, @@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, @@ -342,7 +342,7 @@ namespace Jellyfin.Api.Controllers PersonIds = RequestHelpers.GetGuids(personIds), PersonTypes = RequestHelpers.Split(personTypes, ',', true), Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(), - ImageTypes = RequestHelpers.Split(imageTypes, ',', true).Select(v => Enum.Parse(v, true)).ToArray(), + ImageTypes = imageTypes, VideoTypes = RequestHelpers.Split(videoTypes, ',', true).Select(v => Enum.Parse(v, true)).ToArray(), AdjacentTo = adjacentTo, ItemIds = RequestHelpers.GetGuids(ids), @@ -539,7 +539,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? mediaTypes, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] bool enableTotalRecordCount = true, diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 3557e63047..458c62355c 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -26,6 +26,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; @@ -145,7 +146,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isDisliked, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? fields, [FromQuery] bool? enableUserData, [FromQuery] string? sortBy, @@ -262,7 +263,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? seriesTimerId, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? fields, [FromQuery] bool? enableUserData, [FromQuery] bool? isMovie, @@ -349,7 +350,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? seriesTimerId, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? fields, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) @@ -560,7 +561,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? genreIds, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] string? seriesTimerId, [FromQuery] Guid? librarySeriesId, @@ -661,10 +662,11 @@ namespace Jellyfin.Api.Controllers } } + var imageTypes = RequestHelpers.GetImageTypes(body.EnableImageTypes); var dtoOptions = new DtoOptions() .AddItemFields(body.Fields) .AddClientFields(Request) - .AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes); + .AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, imageTypes); return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); } @@ -704,7 +706,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isSports, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? genreIds, [FromQuery] string? fields, [FromQuery] bool? enableUserData, diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 570ae8fdc7..28337396b1 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -11,6 +11,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -99,7 +100,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 8bd610dad9..94161490aa 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -99,7 +100,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 1e95bd2b38..0419b2436b 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Playlists; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; @@ -151,7 +152,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes) + [FromQuery] ImageType[] enableImageTypes) { var playlist = (Playlist)_libraryManager.GetItemById(playlistId); if (playlist == null) diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index cdd5f958e9..401f516fe2 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -98,7 +99,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 5157b08ae5..e230d34257 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -1,6 +1,7 @@ using System; using Jellyfin.Api.Constants; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -149,7 +150,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, - [FromQuery] string? imageTypes, + [FromQuery] ImageType[] imageTypes, [FromQuery] string? sortBy, [FromQuery] bool? isPlayed, [FromQuery] string? genres, @@ -158,7 +159,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index d158f6c342..49a6c386fe 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -13,6 +13,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? parentId, [FromQuery] bool? enableImges, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { @@ -134,7 +135,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? parentId, [FromQuery] bool? enableImges, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData) { var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -206,7 +207,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] string? sortBy) { @@ -325,7 +326,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? adjacentTo, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData) { var user = userId.HasValue && !userId.Equals(Guid.Empty) diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 48262f0620..a52af17815 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -272,7 +272,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isPlayed, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] int limit = 20, [FromQuery] bool groupItems = true) diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 4ecf0407bf..2c685309a6 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? sortBy, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] string? enableImageTypes, + [FromQuery] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] bool recursive = true, [FromQuery] bool? enableImages = true) diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs index e61e9c29d9..85f8d789e4 100644 --- a/Jellyfin.Api/Extensions/DtoExtensions.cs +++ b/Jellyfin.Api/Extensions/DtoExtensions.cs @@ -126,7 +126,7 @@ namespace Jellyfin.Api.Extensions bool? enableImages, bool? enableUserData, int? imageTypeLimit, - string? enableImageTypes) + ImageType[] enableImageTypes) { dtoOptions.EnableImages = enableImages ?? true; @@ -140,11 +140,9 @@ namespace Jellyfin.Api.Extensions dtoOptions.EnableUserData = enableUserData.Value; } - if (!string.IsNullOrWhiteSpace(enableImageTypes)) + if (enableImageTypes.Length != 0) { - dtoOptions.ImageTypes = enableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)) - .ToArray(); + dtoOptions.ImageTypes = enableImageTypes; } return dtoOptions; diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 8dcf08af56..18200de726 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -6,6 +6,7 @@ using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Http; @@ -173,5 +174,31 @@ namespace Jellyfin.Api.Helpers .Select(i => i!.Value) .ToArray(); } + + /// + /// Gets the item fields. + /// + /// The image types string. + /// IEnumerable{ItemFields}. + internal static ImageType[] GetImageTypes(string? imageTypes) + { + if (string.IsNullOrEmpty(imageTypes)) + { + return Array.Empty(); + } + + return Split(imageTypes, ',', true) + .Select(v => + { + if (Enum.TryParse(v, true, out ImageType value)) + { + return (ImageType?)value; + } + + return null; + }).Where(i => i.HasValue) + .Select(i => i!.Value) + .ToArray(); + } } } From 2eafbc03e44310c6abda435867129100d1587f11 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 18:01:12 -0600 Subject: [PATCH 073/203] fix build --- Jellyfin.Api/Helpers/RequestHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 18200de726..fa48e7a8f1 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -174,7 +174,7 @@ namespace Jellyfin.Api.Helpers .Select(i => i!.Value) .ToArray(); } - + /// /// Gets the item fields. /// From 1bbd8df92b1a5ef1661693810f089d9884e416d3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 9 Oct 2020 18:02:53 -0600 Subject: [PATCH 074/203] Remove references to legacy scripts --- deployment/build.windows.amd64 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64 index afe4905769..a9daa6a239 100755 --- a/deployment/build.windows.amd64 +++ b/deployment/build.windows.amd64 @@ -35,10 +35,6 @@ unzip ${addin_build_dir}/jellyfin-ffmpeg.zip -d ${addin_build_dir}/jellyfin-ffmp cp ${addin_build_dir}/jellyfin-ffmpeg/* ${output_dir} rm -rf ${addin_build_dir} -# Prepare scripts -cp ${SOURCE_DIR}/windows/legacy/install-jellyfin.ps1 ${output_dir}/install-jellyfin.ps1 -cp ${SOURCE_DIR}/windows/legacy/install.bat ${output_dir}/install.bat - # Create zip package pushd dist zip -qr jellyfin-server_${version}.portable.zip jellyfin-server_${version} From 163d6b0f1d1a252e85b8776b4b0c2ccaa611667e Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 11 Oct 2020 06:15:55 -0600 Subject: [PATCH 075/203] Update Jellyfin.Api/Helpers/RequestHelpers.cs Co-authored-by: Claus Vium --- Jellyfin.Api/Helpers/RequestHelpers.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index fa48e7a8f1..0f2967063e 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -196,7 +196,8 @@ namespace Jellyfin.Api.Helpers } return null; - }).Where(i => i.HasValue) + }) + .Where(i => i.HasValue) .Select(i => i!.Value) .ToArray(); } From 7072b4d92633c4728bc7ee55684d43d585e140c9 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sun, 11 Oct 2020 17:01:33 +0200 Subject: [PATCH 076/203] Make StartupWizardCompleted nullable in PublicSystemInfo --- MediaBrowser.Model/System/PublicSystemInfo.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs index d2f7556a51..53030843ae 100644 --- a/MediaBrowser.Model/System/PublicSystemInfo.cs +++ b/MediaBrowser.Model/System/PublicSystemInfo.cs @@ -43,7 +43,10 @@ namespace MediaBrowser.Model.System /// /// Gets or sets a value indicating whether the startup wizard is completed. /// - /// The startup completion status. - public bool StartupWizardCompleted { get; set; } + /// + /// Nullable for OpenAPI specification only to retain backwards compatibility in apiclients. + /// + /// The startup completion status.] + public bool? StartupWizardCompleted { get; set; } } } From ee976bb47abe2f16b7d27cd2f789264c5a9ec8d0 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 11 Oct 2020 15:27:11 -0600 Subject: [PATCH 077/203] Update MediaBrowser.Common/Plugins/LocalPlugin.cs Co-authored-by: BaronGreenback --- MediaBrowser.Common/Plugins/LocalPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index e26631615a..922fad23ae 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Common.Plugins /// Comparison result. public static bool operator !=(LocalPlugin left, LocalPlugin right) { - return !(left == right); + return !left.Equals(right); } /// From 37f38526995146c8731af6d3091113bd3e842017 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 11 Oct 2020 17:45:45 -0600 Subject: [PATCH 078/203] Add comma delimited string to array json converter --- .../JsonCommaDelimitedArrayConverter.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs new file mode 100644 index 0000000000..4f6a685314 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -0,0 +1,53 @@ +using System; +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Convert comma delimited string to array of type. + /// + /// Type to convert to. + public class JsonCommaDelimitedArrayConverter : JsonConverter + { + private readonly TypeConverter _typeConverter; + + /// + /// Initializes a new instance of the class. + /// + public JsonCommaDelimitedArrayConverter() + { + _typeConverter = TypeDescriptor.GetConverter(typeof(T)); + } + + /// + public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var stringEntries = reader.GetString()?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (stringEntries == null || stringEntries.Length == 0) + { + return Array.Empty(); + } + + var entries = new T[stringEntries.Length]; + for (var i = 0; i < stringEntries.Length; i++) + { + entries[i] = (T)_typeConverter.ConvertFrom(stringEntries[i]); + } + + return entries; + } + + return JsonSerializer.Deserialize(ref reader, options); + } + + /// + public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); + } + } +} \ No newline at end of file From 7565ae22cb91c472928167ac89c39f67324c72e1 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 11 Oct 2020 21:09:15 -0600 Subject: [PATCH 079/203] Add tests and switch to factory --- ...JsonCommaDelimitedArrayConverterFactory.cs | 28 ++++++++ .../Json/JsonCommaDelimitedArrayTests.cs | 65 +++++++++++++++++++ .../Models/GenericBodyModel.cs | 20 ++++++ 3 files changed, 113 insertions(+) create mode 100644 MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs create mode 100644 tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs create mode 100644 tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs new file mode 100644 index 0000000000..b7b1daf76a --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs @@ -0,0 +1,28 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Json comma delimited array converter factory. + /// + /// + /// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow. + /// + public class JsonCommaDelimitedArrayConverterFactory : JsonConverterFactory + { + /// + public override bool CanConvert(Type typeToConvert) + { + return true; + } + + /// + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var structType = typeToConvert.GetElementType(); + return (JsonConverter)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType)); + } + } +} \ No newline at end of file diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs new file mode 100644 index 0000000000..c543cfee9f --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs @@ -0,0 +1,65 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Jellyfin.Common.Tests.Models; +using MediaBrowser.Model.Session; +using Xunit; + +namespace Jellyfin.Common.Tests.Json +{ + public static class JsonCommaDelimitedArrayTests + { + [Fact] + public static void Deserialize_String_Valid_Success() + { + var desiredValue = new GenericBodyModel + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Valid_Success() + { + var desiredValue = new GenericBodyModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_String_Array_Valid_Success() + { + var desiredValue = new GenericBodyModel + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""a"",""b"",""c""] }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Array_Valid_Success() + { + var desiredValue = new GenericBodyModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + } +} \ No newline at end of file diff --git a/tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs b/tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs new file mode 100644 index 0000000000..9a6c382fea --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; +using MediaBrowser.Common.Json.Converters; + +namespace Jellyfin.Common.Tests.Models +{ + /// + /// The generic body model. + /// + /// The value type. + public class GenericBodyModel + { + /// + /// Gets or sets the value. + /// + [SuppressMessage("Microsoft.Performance", "CA1819:Properties should not return arrays", MessageId = "Value", Justification = "Imported from ServiceStack")] + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public T[] Value { get; set; } = default!; + } +} \ No newline at end of file From 6f9beef7323cf133af481d003c7db7a7c6abc6dd Mon Sep 17 00:00:00 2001 From: Jan-Pieter Baert Date: Mon, 12 Oct 2020 19:22:33 +0200 Subject: [PATCH 080/203] Fix SA1012 and SA1013 warnings --- .../Data/SqliteItemRepository.cs | 2 +- .../LiveTv/RefreshChannelsScheduledTask.cs | 2 +- .../Tasks/DeleteCacheFileTask.cs | 2 +- .../ScheduledTasks/Tasks/DeleteLogFileTask.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 18 +++++++++--------- .../MediaInfo/SubtitleScheduledTask.cs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index d09f84e174..86a9908bef 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -5357,7 +5357,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type itemCountColumns = new Dictionary() { - { "itemTypes", "(" + itemCountColumnQuery + ") as itemTypes"} + { "itemTypes", "(" + itemCountColumnQuery + ") as itemTypes" } }; } diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index f1b61f7c7d..582b649235 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv return new[] { // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } }; } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 5adcefc1fe..692d1667d2 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return new[] { // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } }; } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index 54e18eaea0..184d155d44 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks { return new[] { - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } }; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index c5529ad5bd..906afa7f6f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -70,15 +70,15 @@ namespace MediaBrowser.Controller.MediaEncoding var codecMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { - {"qsv", hwEncoder + "_qsv"}, - {hwEncoder + "_qsv", hwEncoder + "_qsv"}, - {"nvenc", hwEncoder + "_nvenc"}, - {"amf", hwEncoder + "_amf"}, - {"omx", hwEncoder + "_omx"}, - {hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m"}, - {"mediacodec", hwEncoder + "_mediacodec"}, - {"vaapi", hwEncoder + "_vaapi"}, - {"videotoolbox", hwEncoder + "_videotoolbox"} + { "qsv", hwEncoder + "_qsv" }, + { hwEncoder + "_qsv", hwEncoder + "_qsv" }, + { "nvenc", hwEncoder + "_nvenc" }, + { "amf", hwEncoder + "_amf" }, + { "omx", hwEncoder + "_omx" }, + { hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m" }, + { "mediacodec", hwEncoder + "_mediacodec" }, + { "vaapi", hwEncoder + "_vaapi" }, + { "videotoolbox", hwEncoder + "_videotoolbox" } }; if (!string.IsNullOrEmpty(hwType) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index d231bfa2fc..9804ec3bb4 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -214,7 +214,7 @@ namespace MediaBrowser.Providers.MediaInfo return new[] { // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } }; } } From ec91d3893d6f1aee7f75dabae3d2311f7592440b Mon Sep 17 00:00:00 2001 From: Jan-Pieter Baert Date: Mon, 12 Oct 2020 20:05:11 +0200 Subject: [PATCH 081/203] Fix SA 1116 warnings --- .../Data/BaseSqliteRepository.cs | 3 +- .../Data/SqliteItemRepository.cs | 33 ++++++++++++------- .../Data/SqliteUserDataRepository.cs | 9 +++-- .../Library/Resolvers/Audio/AudioResolver.cs | 6 ++-- .../Library/Resolvers/Books/BookResolver.cs | 3 +- .../Security/AuthenticationRepository.cs | 18 ++++++---- .../Entities/BaseItemExtensions.cs | 3 +- MediaBrowser.Controller/Entities/Folder.cs | 9 +++-- .../Entities/UserViewBuilder.cs | 6 ++-- .../Library/ILibraryManager.cs | 3 +- .../MediaEncoding/EncodingHelper.cs | 3 +- .../MediaEncoding/EncodingJobInfo.cs | 6 ++-- .../MediaEncoding/IMediaEncoder.cs | 3 +- .../Resolvers/IItemResolver.cs | 3 +- .../Dlna/ContentFeatureBuilder.cs | 12 ++++--- MediaBrowser.Model/Dlna/DeviceProfile.cs | 3 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 15 ++++++--- .../Plugins/TheTvdb/TvdbClientManager.cs | 3 +- .../Plugins/TheTvdb/TvdbEpisodeProvider.cs | 3 +- 19 files changed, 96 insertions(+), 48 deletions(-) diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 0fb050a7a5..8c756a7f4c 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -157,7 +157,8 @@ namespace Emby.Server.Implementations.Data protected bool TableExists(ManagedConnection connection, string name) { - return connection.RunInTransaction(db => + return connection.RunInTransaction( + db => { using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master")) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 86a9908bef..1c2aeda709 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -219,7 +219,8 @@ namespace Emby.Server.Implementations.Data { connection.RunQueries(queries); - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { var existingColumnNames = GetColumnNames(db, "AncestorIds"); AddColumn(db, "AncestorIds", "AncestorIdText", "Text", existingColumnNames); @@ -495,7 +496,8 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { @@ -546,7 +548,8 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { SaveItemsInTranscation(db, tuples); }, TransactionMode); @@ -2032,7 +2035,8 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { // First delete chapters db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob); @@ -2921,7 +2925,8 @@ namespace Emby.Server.Implementations.Data var result = new QueryResult(); using (var connection = GetConnection(true)) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { var statements = PrepareAll(db, statementTexts); @@ -3324,7 +3329,8 @@ namespace Emby.Server.Implementations.Data var result = new QueryResult(); using (var connection = GetConnection(true)) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { var statements = PrepareAll(db, statementTexts); @@ -4899,7 +4905,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { connection.ExecuteAll(sql); }, TransactionMode); @@ -4950,7 +4957,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { var idBlob = id.ToByteArray(); @@ -5744,7 +5752,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { var itemIdBlob = itemId.ToByteArray(); @@ -5898,7 +5907,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { var itemIdBlob = id.ToByteArray(); @@ -6232,7 +6242,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { var itemIdBlob = id.ToByteArray(); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 4a78aac8e6..2c4e8e0fcc 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -44,7 +44,8 @@ namespace Emby.Server.Implementations.Data var users = userDatasTableExists ? null : userManager.Users; - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { db.ExecuteAll(string.Join(";", new[] { @@ -178,7 +179,8 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { SaveUserData(db, internalUserId, key, userData); }, TransactionMode); @@ -246,7 +248,8 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { foreach (var userItemData in userDataList) { diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 03059e6d35..70be524116 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -32,7 +32,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// The priority. public override ResolverPriority Priority => ResolverPriority.Fourth; - public MultiItemResolverResult ResolveMultiple(Folder parent, + public MultiItemResolverResult ResolveMultiple( + Folder parent, List files, string collectionType, IDirectoryService directoryService) @@ -50,7 +51,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio return result; } - private MultiItemResolverResult ResolveMultipleInternal(Folder parent, + private MultiItemResolverResult ResolveMultipleInternal( + Folder parent, List files, string collectionType, IDirectoryService directoryService) diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 86a5d8b7d8..59af7ce8ac 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -50,7 +50,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books var fileExtension = Path.GetExtension(f.FullName) ?? string.Empty; - return _validExtensions.Contains(fileExtension, + return _validExtensions.Contains( + fileExtension, StringComparer .OrdinalIgnoreCase); }).ToList(); diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 29393ae07d..4bc12f44a4 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -54,7 +54,8 @@ namespace Emby.Server.Implementations.Security { if (tableNewlyCreated && TableExists(connection, "AccessTokens")) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { var existingColumnNames = GetColumnNames(db, "AccessTokens"); @@ -88,7 +89,8 @@ namespace Emby.Server.Implementations.Security using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)")) { @@ -119,7 +121,8 @@ namespace Emby.Server.Implementations.Security using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id")) { @@ -151,7 +154,8 @@ namespace Emby.Server.Implementations.Security using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id")) { @@ -346,7 +350,8 @@ namespace Emby.Server.Implementations.Security { using (var connection = GetConnection(true)) { - return connection.RunInTransaction(db => + return connection.RunInTransaction( + db => { using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId")) { @@ -377,7 +382,8 @@ namespace Emby.Server.Implementations.Security using (var connection = GetConnection()) { - connection.RunInTransaction(db => + connection.RunInTransaction( + db => { using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))")) { diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index 8a69971d0f..c65477d39a 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -45,7 +45,8 @@ namespace MediaBrowser.Controller.Entities { if (file.StartsWith("http", System.StringComparison.OrdinalIgnoreCase)) { - item.SetImage(new ItemImageInfo + item.SetImage( + new ItemImageInfo { Path = file, Type = imageType diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 901ea875bc..35ddaffadc 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -255,7 +255,8 @@ namespace MediaBrowser.Controller.Entities var id = child.Id; if (dictionary.ContainsKey(id)) { - Logger.LogError("Found folder containing items with duplicate id. Path: {path}, Child Name: {ChildName}", + Logger.LogError( + "Found folder containing items with duplicate id. Path: {path}, Child Name: {ChildName}", Path ?? Name, child.Path ?? child.Name); } @@ -984,7 +985,8 @@ namespace MediaBrowser.Controller.Entities return items; } - private static bool CollapseBoxSetItems(InternalItemsQuery query, + private static bool CollapseBoxSetItems( + InternalItemsQuery query, BaseItem queryParent, User user, IServerConfigurationManager configurationManager) @@ -1593,7 +1595,8 @@ namespace MediaBrowser.Controller.Entities /// The date played. /// if set to true [reset position]. /// Task. - public override void MarkPlayed(User user, + public override void MarkPlayed( + User user, DateTime? datePlayed, bool resetPosition) { diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 068a767698..7bb311900e 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -343,7 +343,8 @@ namespace MediaBrowser.Controller.Entities { var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows, string.Empty }); - var result = _tvSeriesManager.GetNextUp(new NextUpQuery + var result = _tvSeriesManager.GetNextUp( + new NextUpQuery { Limit = query.Limit, StartIndex = query.StartIndex, @@ -443,7 +444,8 @@ namespace MediaBrowser.Controller.Entities return Filter(item, query.User, query, BaseItem.UserDataManager, BaseItem.LibraryManager); } - public static QueryResult PostFilterAndSort(IEnumerable items, + public static QueryResult PostFilterAndSort( + IEnumerable items, BaseItem queryParent, int? totalRecordLimit, InternalItemsQuery query, diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 332730bcca..32703c2fd1 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -566,7 +566,8 @@ namespace MediaBrowser.Controller.Library int GetCount(InternalItemsQuery query); - void AddExternalSubtitleStreams(List streams, + void AddExternalSubtitleStreams( + List streams, string videoPath, string[] files); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 906afa7f6f..f913c32e43 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1822,7 +1822,8 @@ namespace MediaBrowser.Controller.MediaEncoding return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); } - public List GetScalingFilters(EncodingJobInfo state, + public List GetScalingFilters( + EncodingJobInfo state, int? videoWidth, int? videoHeight, Video3DFormat? threedFormat, diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index c7ec878d2d..6cd0c70d2f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -342,7 +342,8 @@ namespace MediaBrowser.Controller.MediaEncoding { var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value); - var newSize = DrawingUtils.Resize(size, + var newSize = DrawingUtils.Resize( + size, BaseRequest.Width ?? 0, BaseRequest.Height ?? 0, BaseRequest.MaxWidth ?? 0, @@ -368,7 +369,8 @@ namespace MediaBrowser.Controller.MediaEncoding { var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value); - var newSize = DrawingUtils.Resize(size, + var newSize = DrawingUtils.Resize( + size, BaseRequest.Width ?? 0, BaseRequest.Height ?? 0, BaseRequest.MaxWidth ?? 0, diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 17d6dc5d27..f6bc1f4de9 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -68,7 +68,8 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Extracts the video images on interval. /// - Task ExtractVideoImagesOnInterval(string[] inputFiles, + Task ExtractVideoImagesOnInterval( + string[] inputFiles, string container, MediaStream videoStream, MediaProtocol protocol, diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs index eb7fb793a4..75286eadc0 100644 --- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs @@ -29,7 +29,8 @@ namespace MediaBrowser.Controller.Resolvers public interface IMultiItemResolver { - MultiItemResolverResult ResolveMultiple(Folder parent, + MultiItemResolverResult ResolveMultiple( + Folder parent, List files, string collectionType, IDirectoryService directoryService); diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 93e60753ae..8b73ecbd4d 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -38,7 +38,8 @@ namespace MediaBrowser.Model.Dlna ";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue)); - ResponseProfile mediaProfile = _profile.GetImageMediaProfile(container, + ResponseProfile mediaProfile = _profile.GetImageMediaProfile( + container, width, height); @@ -160,7 +161,8 @@ namespace MediaBrowser.Model.Dlna string dlnaflags = string.Format(CultureInfo.InvariantCulture, ";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue)); - ResponseProfile mediaProfile = _profile.GetVideoMediaProfile(container, + ResponseProfile mediaProfile = _profile.GetVideoMediaProfile( + container, audioCodec, videoCodec, width, @@ -221,7 +223,8 @@ namespace MediaBrowser.Model.Dlna private static string GetImageOrgPnValue(string container, int? width, int? height) { MediaFormatProfile? format = new MediaFormatProfileResolver() - .ResolveImageFormat(container, + .ResolveImageFormat( + container, width, height); @@ -231,7 +234,8 @@ namespace MediaBrowser.Model.Dlna private static string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels) { MediaFormatProfile? format = new MediaFormatProfileResolver() - .ResolveAudioFormat(container, + .ResolveAudioFormat( + container, audioBitrate, audioSampleRate, audioChannels); diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 7e921b1fdf..44412f3e41 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -277,7 +277,8 @@ namespace MediaBrowser.Model.Dlna return null; } - public ResponseProfile GetVideoMediaProfile(string container, + public ResponseProfile GetVideoMediaProfile( + string container, string audioCodec, string videoCodec, int? width, diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index fc0aad0727..4959a9b922 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -455,7 +455,8 @@ namespace MediaBrowser.Model.Dlna if (directPlayProfile == null) { - _logger.LogInformation("Profile: {0}, No audio direct play profiles found for {1} with codec {2}", + _logger.LogInformation( + "Profile: {0}, No audio direct play profiles found for {1} with codec {2}", options.Profile.Name ?? "Unknown Profile", item.Path ?? "Unknown path", audioStream.Codec ?? "Unknown codec"); @@ -679,7 +680,8 @@ namespace MediaBrowser.Model.Dlna bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1); bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.Item1); - _logger.LogInformation("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", + _logger.LogInformation( + "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", options.Profile.Name ?? "Unknown Profile", item.Path ?? "Unknown path", isEligibleForDirectPlay, @@ -973,7 +975,8 @@ namespace MediaBrowser.Model.Dlna if (directPlay == null) { - _logger.LogInformation("Profile: {0}, No video direct play profiles found for {1} with codec {2}", + _logger.LogInformation( + "Profile: {0}, No video direct play profiles found for {1} with codec {2}", profile.Name ?? "Unknown Profile", mediaSource.Path ?? "Unknown path", videoStream.Codec ?? "Unknown codec"); @@ -1137,7 +1140,8 @@ namespace MediaBrowser.Model.Dlna private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource) { - _logger.LogInformation("Profile: {0}, DirectPlay=false. Reason={1}.{2} Condition: {3}. ConditionValue: {4}. IsRequired: {5}. Path: {6}", + _logger.LogInformation( + "Profile: {0}, DirectPlay=false. Reason={1}.{2} Condition: {3}. ConditionValue: {4}. IsRequired: {5}. Path: {6}", type, profile.Name ?? "Unknown Profile", condition.Property, @@ -1342,7 +1346,8 @@ namespace MediaBrowser.Model.Dlna if (itemBitrate > requestedMaxBitrate) { - _logger.LogInformation("Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", + _logger.LogInformation( + "Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", playMethod, itemBitrate, requestedMaxBitrate); return false; } diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs index 5e9a4a2252..ce0dab701d 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs @@ -150,7 +150,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb string language, CancellationToken cancellationToken) { - searchInfo.SeriesProviderIds.TryGetValue(nameof(MetadataProvider.Tvdb), + searchInfo.SeriesProviderIds.TryGetValue( + nameof(MetadataProvider.Tvdb), out var seriesTvdbId); var episodeQuery = new EpisodeQuery(); diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs index 5fa8a3e1ca..fd72ea4a81 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs @@ -106,7 +106,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb .ConfigureAwait(false); if (string.IsNullOrEmpty(episodeTvdbId)) { - _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}", + _logger.LogError( + "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}", searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId); return result; } From 74f4affcda2c0c8e6eebb96f8173533989675e66 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 12 Oct 2020 20:09:15 +0200 Subject: [PATCH 082/203] Fix AudioBookListResolver test coverage --- Emby.Naming/AudioBook/AudioBookResolver.cs | 17 ++++----------- .../AudioBook/AudioBookResolverTests.cs | 21 ++++++++++++------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index ed53bd04fa..5807d4688c 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -1,3 +1,4 @@ +#nullable enable #pragma warning disable CS1591 using System; @@ -16,21 +17,11 @@ namespace Emby.Naming.AudioBook _options = options; } - public AudioBookFileInfo ParseFile(string path) + public AudioBookFileInfo? Resolve(string path, bool isDirectory = false) { - return Resolve(path, false); - } - - public AudioBookFileInfo ParseDirectory(string path) - { - return Resolve(path, true); - } - - public AudioBookFileInfo Resolve(string path, bool isDirectory = false) - { - if (string.IsNullOrEmpty(path)) + if (path.Length == 0) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException("String can't be empty.", nameof(path)); } // TODO diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index 83d44721c4..673289436d 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Emby.Naming.AudioBook; using Emby.Naming.Common; using Xunit; @@ -42,16 +43,22 @@ namespace Jellyfin.Naming.Tests.AudioBook [Theory] [MemberData(nameof(GetResolveFileTestData))] - public void ResolveFile_ValidFileName_Success(AudioBookFileInfo expectedResult) + public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult) { var result = new AudioBookResolver(_namingOptions).Resolve(expectedResult.Path); Assert.NotNull(result); - Assert.Equal(result.Path, expectedResult.Path); - Assert.Equal(result.Container, expectedResult.Container); - Assert.Equal(result.ChapterNumber, expectedResult.ChapterNumber); - Assert.Equal(result.PartNumber, expectedResult.PartNumber); - Assert.Equal(result.IsDirectory, expectedResult.IsDirectory); + Assert.Equal(result!.Path, expectedResult.Path); + Assert.Equal(result!.Container, expectedResult.Container); + Assert.Equal(result!.ChapterNumber, expectedResult.ChapterNumber); + Assert.Equal(result!.PartNumber, expectedResult.PartNumber); + Assert.Equal(result!.IsDirectory, expectedResult.IsDirectory); + } + + [Fact] + public void Resolve_EmptyFileName_ArgumentException() + { + Assert.Throws(() => new AudioBookResolver(_namingOptions).Resolve(string.Empty)); } } } From f998e521072ac5d3c0725cbe9fb544e005b15dfe Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 13 Oct 2020 18:50:04 -0600 Subject: [PATCH 083/203] Update to dotnet 3.1.9 --- .../Emby.Server.Implementations.csproj | 8 ++++---- Jellyfin.Api/Jellyfin.Api.csproj | 4 ++-- Jellyfin.Data/Jellyfin.Data.csproj | 4 ++-- .../Jellyfin.Server.Implementations.csproj | 4 ++-- Jellyfin.Server/Jellyfin.Server.csproj | 8 ++++---- MediaBrowser.Common/MediaBrowser.Common.csproj | 4 ++-- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 4 ++-- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 6 +++--- deployment/Dockerfile.debian.amd64 | 2 +- deployment/Dockerfile.debian.arm64 | 2 +- deployment/Dockerfile.debian.armhf | 2 +- deployment/Dockerfile.linux.amd64 | 2 +- deployment/Dockerfile.macos | 2 +- deployment/Dockerfile.portable | 2 +- deployment/Dockerfile.ubuntu.amd64 | 2 +- deployment/Dockerfile.ubuntu.arm64 | 2 +- deployment/Dockerfile.ubuntu.armhf | 2 +- deployment/Dockerfile.windows.amd64 | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 4 ++-- 20 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 9ed3cca99c..c762aa0b84 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -32,10 +32,10 @@ - - - - + + + + diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 6a00db4b1a..da0852cebc 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -14,9 +14,9 @@ - + - + diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 6bb0d8ce27..5038988f96 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -41,8 +41,8 @@ - - + + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 17ba092588..c52be3b8a9 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -25,11 +25,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 761a92f6db..7de34f4160 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -38,10 +38,10 @@ - - - - + + + + diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 322740cca8..e716a6610f 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 6544704065..4374317d67 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 2646810907..253ee7e795 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -34,7 +34,7 @@ - + diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 794490cc52..24400eae53 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index 7202c58838..aaca8fe01e 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index e9f30213f4..594da04ceb 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index 91a8a6e7a1..3e6e2d0d70 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 828d5c2cf9..f98881ebfe 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos index 0b2a0fe5fa..ec9d2d8c77 100644 --- a/deployment/Dockerfile.macos +++ b/deployment/Dockerfile.macos @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index 7d5de230fa..3523f8aceb 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index 9c63f43dfa..0a365e1aee 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index 51612dd443..ab3ec9b9f8 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 4ed7f86872..fa41bdf48a 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index 5671cc598a..7216b2363b 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/f01e3d97-c1c3-4635-bc77-0c893be36820/6ec6acabc22468c6cc68b61625b14a7d/dotnet-sdk-3.1.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-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 diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 8a559b7b62..aae436fb73 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -16,8 +16,8 @@ - - + + From 15a7f88e083a78a4219cbd004e6041b49c490b05 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 14 Oct 2020 11:44:11 -0600 Subject: [PATCH 084/203] Automatically clean activity log database --- .../Localization/Core/en-US.json | 2 + .../Tasks/CleanActivityLogTask.cs | 68 +++++++++++++++++++ .../Activity/ActivityManager.cs | 12 ++++ .../Activity/IActivityManager.cs | 7 ++ 4 files changed, 89 insertions(+) create mode 100644 Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 92c54fb0e6..bc973c9737 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -95,6 +95,8 @@ "TasksLibraryCategory": "Library", "TasksApplicationCategory": "Application", "TasksChannelsCategory": "Internet Channels", + "TaskCleanActivityLog": "Clean Activity Log", + "TaskCleanActivityLogDescription": "Deletes activity log entries older then the configured age.", "TaskCleanCache": "Clean Cache Directory", "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", "TaskRefreshChapterImages": "Extract Chapter Images", diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs new file mode 100644 index 0000000000..50bc091c83 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks +{ + /// + /// Deletes old activity log entries. + /// + public class CleanActivityLogTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILocalizationManager _localization; + private readonly IActivityManager _activityManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public CleanActivityLogTask( + ILocalizationManager localization, + IActivityManager activityManager) + { + _localization = localization; + _activityManager = activityManager; + } + + /// + public string Name => _localization.GetLocalizedString("TaskCleanActivityLog"); + + /// + public string Key => "CleanActivityLog"; + + /// + public string Description => _localization.GetLocalizedString("TaskCleanActivityLogDescription"); + + /// + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// + public bool IsHidden => false; + + /// + public bool IsEnabled => true; + + /// + public bool IsLogged => true; + + /// + public Task Execute(CancellationToken cancellationToken, IProgress progress) + { + // TODO allow configure + var startDate = DateTime.UtcNow.AddDays(-30); + return _activityManager.CleanAsync(startDate); + } + + /// + public IEnumerable GetDefaultTriggers() + { + return Enumerable.Empty(); + } + } +} \ No newline at end of file diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 5926abfe0d..7bde4f35be 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -72,6 +72,18 @@ namespace Jellyfin.Server.Implementations.Activity }; } + /// + public async Task CleanAsync(DateTime startDate) + { + await using var dbContext = _provider.CreateContext(); + var entries = dbContext.ActivityLogs + .AsQueryable() + .Where(entry => entry.DateCreated <= startDate); + + dbContext.RemoveRange(entries); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) { return new ActivityLogEntry diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index 3e4ea208ed..28073fb8d7 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -16,5 +16,12 @@ namespace MediaBrowser.Model.Activity Task CreateAsync(ActivityLog entry); Task> GetPagedResultAsync(ActivityLogQuery query); + + /// + /// Remove all activity logs before the specified date. + /// + /// Activity log start date. + /// A representing the asynchronous operation. + Task CleanAsync(DateTime startDate); } } From f2a86d9c80748b39c4bd798882dea79b7f345302 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 14 Oct 2020 13:03:36 -0600 Subject: [PATCH 085/203] Remove CommaDelimitedArrayModelBinderProvider --- Jellyfin.Api/Controllers/ArtistsController.cs | 5 ++-- .../Controllers/ChannelsController.cs | 5 ++-- Jellyfin.Api/Controllers/GenresController.cs | 3 +- Jellyfin.Api/Controllers/ItemsController.cs | 5 ++-- .../Controllers/LibraryStructureController.cs | 3 +- .../Controllers/MusicGenresController.cs | 3 +- Jellyfin.Api/Controllers/PersonsController.cs | 3 +- Jellyfin.Api/Controllers/SessionController.cs | 2 +- Jellyfin.Api/Controllers/StudiosController.cs | 3 +- .../Controllers/TrailersController.cs | 5 ++-- .../CommaDelimitedArrayModelBinderProvider.cs | 29 ------------------- .../ApiServiceCollectionExtensions.cs | 3 -- 12 files changed, 23 insertions(+), 46 deletions(-) delete mode 100644 Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinderProvider.cs diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 784ecaeb32..37fc520b3a 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -4,6 +4,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -89,7 +90,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, @@ -298,7 +299,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index fda00b8d46..20fc96ba83 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -121,7 +122,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? sortOrder, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] string? sortBy, [FromQuery] string? fields) { @@ -196,7 +197,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] string? fields, [FromQuery] string? channelIds) { diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index f4b69b312e..18ec0fe046 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -5,6 +5,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -90,7 +91,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index b50b1e836b..c5f3a1aee1 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -5,6 +5,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -159,7 +160,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isHd, [FromQuery] bool? is4K, [FromQuery] string? locationTypes, - [FromQuery] LocationType[] excludeLocationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, @@ -182,7 +183,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? imageTypes, diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index d290e3c5bd..94995650cb 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; +using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LibraryStructureDto; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -75,7 +76,7 @@ namespace Jellyfin.Api.Controllers public async Task AddVirtualFolder( [FromQuery] string? name, [FromQuery] string? collectionType, - [FromQuery] string[] paths, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] paths, [FromBody] AddVirtualFolderDto? libraryOptionsDto, [FromQuery] bool refreshLibrary = false) { diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index d216b7f728..44616304f7 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -5,6 +5,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -89,7 +90,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index bbef1ff9a1..5221595d86 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -5,6 +5,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -89,7 +90,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 5656709629..e506ac7bf1 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -379,7 +379,7 @@ namespace Jellyfin.Api.Controllers public ActionResult PostCapabilities( [FromQuery] string? id, [FromQuery] string? playableMediaTypes, - [FromQuery] GeneralCommandType[] supportedCommands, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, [FromQuery] bool supportsMediaControl = false, [FromQuery] bool supportsSync = false, [FromQuery] bool supportsPersistentIdentifier = true) diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index 3a8ac1b243..da161f13e3 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -4,6 +4,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -88,7 +89,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? genres, diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index dec4498575..37b9687cd2 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -1,5 +1,6 @@ using System; using Jellyfin.Api.Constants; +using Jellyfin.Api.ModelBinders; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -125,7 +126,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isHd, [FromQuery] bool? is4K, [FromQuery] string? locationTypes, - [FromQuery] LocationType[] excludeLocationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, @@ -147,7 +148,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? parentId, [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, - [FromQuery] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, [FromQuery] string? imageTypes, diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinderProvider.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinderProvider.cs deleted file mode 100644 index b9785a73b8..0000000000 --- a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinderProvider.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.AspNetCore.Mvc.ModelBinding; - -namespace Jellyfin.Api.ModelBinders -{ - /// - /// Comma delimited array model binder provider. - /// - public class CommaDelimitedArrayModelBinderProvider : IModelBinderProvider - { - private readonly IModelBinder _binder; - - /// - /// Initializes a new instance of the class. - /// - public CommaDelimitedArrayModelBinderProvider() - { - _binder = new CommaDelimitedArrayModelBinder(); - } - - /// - public IModelBinder? GetBinder(ModelBinderProviderContext context) - { - return context.Metadata.ModelType.IsArray ? _binder : null; - } - } -} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index d7b9da5c2f..e180d0cd7a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -16,7 +16,6 @@ using Jellyfin.Api.Auth.LocalAccessPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; -using Jellyfin.Api.ModelBinders; using Jellyfin.Server.Configuration; using Jellyfin.Server.Filters; using Jellyfin.Server.Formatters; @@ -167,8 +166,6 @@ namespace Jellyfin.Server.Extensions opts.OutputFormatters.Add(new CssOutputFormatter()); opts.OutputFormatters.Add(new XmlOutputFormatter()); - - opts.ModelBinderProviders.Insert(0, new CommaDelimitedArrayModelBinderProvider()); }) // Clear app parts to avoid other assemblies being picked up From 39924f99927ae85b85095cfe9c2d7fb4ece7e75a Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 14 Oct 2020 17:58:33 -0600 Subject: [PATCH 086/203] Allow apikey to authenticate as admin --- .../HttpServer/Security/AuthService.cs | 7 +- .../Security/AuthorizationContext.cs | 114 +++++++++--------- Jellyfin.Api/Auth/BaseAuthorizationHandler.cs | 9 +- .../Auth/CustomAuthenticationHandler.cs | 14 +-- 4 files changed, 74 insertions(+), 70 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 68d981ad1f..50c5b5b79e 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -19,12 +19,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public AuthorizationInfo Authenticate(HttpRequest request) { var auth = _authorizationContext.GetAuthorizationInfo(request); - if (auth?.User == null) - { - return null; - } - - if (auth.User.HasPermission(PermissionKind.IsDisabled)) + if (auth.User?.HasPermission(PermissionKind.IsDisabled) ?? false) { throw new SecurityException("User account has been disabled."); } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 4b407dd9d2..c7666452c2 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -111,82 +111,84 @@ namespace Emby.Server.Implementations.HttpServer.Security Token = token }; - AuthenticationInfo originalAuthenticationInfo = null; - if (!string.IsNullOrWhiteSpace(token)) + if (string.IsNullOrWhiteSpace(token)) { - var result = _authRepo.Get(new AuthenticationInfoQuery - { - AccessToken = token - }); + // Request doesn't contain a token. + throw new SecurityException("Unauthorized."); + } - originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null; + var result = _authRepo.Get(new AuthenticationInfoQuery + { + AccessToken = token + }); - if (originalAuthenticationInfo != null) - { - var updateToken = false; + var originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null; - // TODO: Remove these checks for IsNullOrWhiteSpace - if (string.IsNullOrWhiteSpace(authInfo.Client)) - { - authInfo.Client = originalAuthenticationInfo.AppName; - } + if (originalAuthenticationInfo != null) + { + var updateToken = false; - if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) - { - authInfo.DeviceId = originalAuthenticationInfo.DeviceId; - } + // TODO: Remove these checks for IsNullOrWhiteSpace + if (string.IsNullOrWhiteSpace(authInfo.Client)) + { + authInfo.Client = originalAuthenticationInfo.AppName; + } - // Temporary. TODO - allow clients to specify that the token has been shared with a casting device - var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1; + if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) + { + authInfo.DeviceId = originalAuthenticationInfo.DeviceId; + } - if (string.IsNullOrWhiteSpace(authInfo.Device)) - { - authInfo.Device = originalAuthenticationInfo.DeviceName; - } - else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) - { - if (allowTokenInfoUpdate) - { - updateToken = true; - originalAuthenticationInfo.DeviceName = authInfo.Device; - } - } + // Temporary. TODO - allow clients to specify that the token has been shared with a casting device + var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1; - if (string.IsNullOrWhiteSpace(authInfo.Version)) - { - authInfo.Version = originalAuthenticationInfo.AppVersion; - } - else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrWhiteSpace(authInfo.Device)) + { + authInfo.Device = originalAuthenticationInfo.DeviceName; + } + else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) + { + if (allowTokenInfoUpdate) { - if (allowTokenInfoUpdate) - { - updateToken = true; - originalAuthenticationInfo.AppVersion = authInfo.Version; - } + updateToken = true; + originalAuthenticationInfo.DeviceName = authInfo.Device; } + } - if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3) + if (string.IsNullOrWhiteSpace(authInfo.Version)) + { + authInfo.Version = originalAuthenticationInfo.AppVersion; + } + else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) + { + if (allowTokenInfoUpdate) { - originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow; updateToken = true; + originalAuthenticationInfo.AppVersion = authInfo.Version; } + } - if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty)) - { - authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId); + if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3) + { + originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow; + updateToken = true; + } - if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase)) - { - originalAuthenticationInfo.UserName = authInfo.User.Username; - updateToken = true; - } - } + if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty)) + { + authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId); - if (updateToken) + if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase)) { - _authRepo.Update(originalAuthenticationInfo); + originalAuthenticationInfo.UserName = authInfo.User.Username; + updateToken = true; } } + + if (updateToken) + { + _authRepo.Update(originalAuthenticationInfo); + } } return (authInfo, originalAuthenticationInfo); diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs index d732b6bc6a..c4567d058c 100644 --- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs @@ -1,4 +1,5 @@ -using System.Security.Claims; +using System; +using System.Security.Claims; using Jellyfin.Api.Helpers; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; @@ -57,6 +58,12 @@ namespace Jellyfin.Api.Auth return false; } + // UserId of Guid.Empty means token is an apikey. + if (userId.Equals(Guid.Empty)) + { + return true; + } + // Ensure userId links to a valid user. var user = _userManager.GetUserById(userId.Value); if (user == null) diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index 733c6959eb..ec5d172a21 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -1,3 +1,4 @@ +using System; using System.Globalization; using System.Security.Authentication; using System.Security.Claims; @@ -43,18 +44,17 @@ namespace Jellyfin.Api.Auth try { var authorizationInfo = _authService.Authenticate(Request); - if (authorizationInfo == null) + var role = UserRoles.User; + // UserId of Guid.Empty means token is an apikey. + if (authorizationInfo.UserId.Equals(Guid.Empty) || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) { - return Task.FromResult(AuthenticateResult.NoResult()); - // TODO return when legacy API is removed. - // Don't spam the log with "Invalid User" - // return Task.FromResult(AuthenticateResult.Fail("Invalid user")); + role = UserRoles.Administrator; } var claims = new[] { - new Claim(ClaimTypes.Name, authorizationInfo.User.Username), - new Claim(ClaimTypes.Role, authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User), + new Claim(ClaimTypes.Name, authorizationInfo.User?.Username ?? string.Empty), + new Claim(ClaimTypes.Role, role), new Claim(InternalClaimTypes.UserId, authorizationInfo.UserId.ToString("N", CultureInfo.InvariantCulture)), new Claim(InternalClaimTypes.DeviceId, authorizationInfo.DeviceId), new Claim(InternalClaimTypes.Device, authorizationInfo.Device), From c037d3536d44b32de81a43425fb276ec4af5d103 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 14 Oct 2020 17:58:46 -0600 Subject: [PATCH 087/203] clean up authorization handlers --- .../DefaultAuthorizationHandler.cs | 8 +++++--- .../IgnoreParentalControlHandler.cs | 8 +++++--- Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs | 6 +++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs index b5913daab9..be77b7a4e4 100644 --- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs @@ -29,13 +29,15 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement) { var validated = ValidateClaims(context.User); - if (!validated) + if (validated) + { + context.Succeed(requirement); + } + else { context.Fail(); - return Task.CompletedTask; } - context.Succeed(requirement); return Task.CompletedTask; } } diff --git a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs index 5213bc4cb7..a7623556a9 100644 --- a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs +++ b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs @@ -29,13 +29,15 @@ namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreParentalControlRequirement requirement) { var validated = ValidateClaims(context.User, ignoreSchedule: true); - if (!validated) + if (validated) + { + context.Succeed(requirement); + } + else { context.Fail(); - return Task.CompletedTask; } - context.Succeed(requirement); return Task.CompletedTask; } } diff --git a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs b/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs index af73352bcc..d772ec5542 100644 --- a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs +++ b/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs @@ -29,13 +29,13 @@ namespace Jellyfin.Api.Auth.LocalAccessPolicy protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessRequirement requirement) { var validated = ValidateClaims(context.User, localAccessOnly: true); - if (!validated) + if (validated) { - context.Fail(); + context.Succeed(requirement); } else { - context.Succeed(requirement); + context.Fail(); } return Task.CompletedTask; From 58b82b886ff6594e4b521fd4fbbf2cbe8142edea Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 14 Oct 2020 18:16:58 -0600 Subject: [PATCH 088/203] Add npmAuthenticate task --- .ci/azure-pipelines-api-client.yml | 6 ++++++ .npmrc | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 .npmrc diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index fc89b90d4e..de6bbf04ce 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -28,6 +28,12 @@ jobs: inputs: script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar" +## Authenticate with npm registry + - task: npmAuthenticate@0 + inputs: + workingFile: ./.npmrc + customEndpoint: 'jellyfin-bot for NPM' + ## Generate npm api client # Unstable - task: CmdLine@2 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..fcbd4bcf6f --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +registry=https://registry.npmjs.org/ +@{unstable}:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/ +always-auth=true \ No newline at end of file From 4b6889615b877448b0a895b36c835e8d332584c6 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 14 Oct 2020 18:21:15 -0600 Subject: [PATCH 089/203] Fix tests --- tests/Jellyfin.Api.Tests/TestHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Api.Tests/TestHelpers.cs b/tests/Jellyfin.Api.Tests/TestHelpers.cs index a4dd4e4092..c4ce398859 100644 --- a/tests/Jellyfin.Api.Tests/TestHelpers.cs +++ b/tests/Jellyfin.Api.Tests/TestHelpers.cs @@ -45,7 +45,7 @@ namespace Jellyfin.Api.Tests { new Claim(ClaimTypes.Role, role), new Claim(ClaimTypes.Name, "jellyfin"), - new Claim(InternalClaimTypes.UserId, Guid.Empty.ToString("N", CultureInfo.InvariantCulture)), + new Claim(InternalClaimTypes.UserId, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)), new Claim(InternalClaimTypes.DeviceId, Guid.Empty.ToString("N", CultureInfo.InvariantCulture)), new Claim(InternalClaimTypes.Device, "test"), new Claim(InternalClaimTypes.Client, "test"), From eca56dbe6aedc7ef7a10c7bc6ff379be1f6a144c Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 15 Oct 2020 07:31:42 -0600 Subject: [PATCH 090/203] update scope --- .npmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.npmrc b/.npmrc index fcbd4bcf6f..ac84ce9a84 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,3 @@ registry=https://registry.npmjs.org/ -@{unstable}:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/ +@{jellyfin}:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/ always-auth=true \ No newline at end of file From d5c226b1c3b04fa824adbcdc3eb0cbe09815f643 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 15 Oct 2020 08:02:59 -0600 Subject: [PATCH 091/203] Move SecurityException --- .../HttpServer/Security/AuthService.cs | 5 +++++ .../HttpServer/Security/AuthorizationContext.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 50c5b5b79e..7d53e886f7 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -19,6 +19,11 @@ namespace Emby.Server.Implementations.HttpServer.Security public AuthorizationInfo Authenticate(HttpRequest request) { var auth = _authorizationContext.GetAuthorizationInfo(request); + if (auth == null) + { + throw new SecurityException("Unauthenticated request."); + } + if (auth.User?.HasPermission(PermissionKind.IsDisabled) ?? false) { throw new SecurityException("User account has been disabled."); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index c7666452c2..1f647b78bc 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -114,7 +114,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (string.IsNullOrWhiteSpace(token)) { // Request doesn't contain a token. - throw new SecurityException("Unauthorized."); + return (null, null); } var result = _authRepo.Get(new AuthenticationInfoQuery From b2662894cf6e7e6b20d12ff22c79ee5159658a2a Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 16 Oct 2020 09:37:35 -0600 Subject: [PATCH 092/203] Add whitespace handling and tests --- .../JsonCommaDelimitedArrayConverter.cs | 2 +- .../Json/JsonCommaDelimitedArrayTests.cs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs index 4f6a685314..bf7048c370 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Common.Json.Converters var entries = new T[stringEntries.Length]; for (var i = 0; i < stringEntries.Length; i++) { - entries[i] = (T)_typeConverter.ConvertFrom(stringEntries[i]); + entries[i] = (T)_typeConverter.ConvertFrom(stringEntries[i].Trim()); } return entries; diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs index c543cfee9f..16f8a690e6 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs @@ -21,6 +21,19 @@ namespace Jellyfin.Common.Tests.Json Assert.Equal(desiredValue.Value, value?.Value); } + [Fact] + public static void Deserialize_String_Space_Valid_Success() + { + var desiredValue = new GenericBodyModel + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a, b, c"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + [Fact] public static void Deserialize_GenericCommandType_Valid_Success() { @@ -35,6 +48,20 @@ namespace Jellyfin.Common.Tests.Json Assert.Equal(desiredValue.Value, value?.Value); } + [Fact] + public static void Deserialize_GenericCommandType_Space_Valid_Success() + { + var desiredValue = new GenericBodyModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + [Fact] public static void Deserialize_String_Array_Valid_Success() { From b1f637684dfd3511c2f6be643efa5ff7cecadb17 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Fri, 16 Oct 2020 11:15:42 -0700 Subject: [PATCH 093/203] Apply suggestions from code review (updating comments) Co-authored-by: BaronGreenback --- .../Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index bf3088aa83..31f0123dcf 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -757,7 +757,7 @@ namespace MediaBrowser.Providers.Music if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) { - // MusicBrainz is extremely adamant about limiting to one request per second + // MusicBrainz is extremely adamant about limiting to one request per second. var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); } @@ -770,7 +770,7 @@ namespace MediaBrowser.Providers.Music using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl); // MusicBrainz request a contact email address is supplied, as comment, in user agent field: - // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent . request.Headers.UserAgent.ParseAdd(string.Format( CultureInfo.InvariantCulture, "{0} ( {1} )", @@ -779,11 +779,11 @@ namespace MediaBrowser.Providers.Music response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(request).ConfigureAwait(false); - // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) + // We retry a finite number of times, and only whilst MB is indicating 503 (throttling). } while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); - // Log error if unable to query MB database due to throttling + // Log error if unable to query MB database due to throttling. if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) { _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); From 49ac4c4044b1777dc3a25544aead7b0b15b953e8 Mon Sep 17 00:00:00 2001 From: Gubb Jonas Date: Sat, 17 Oct 2020 08:33:58 +0000 Subject: [PATCH 094/203] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 12fda8a986..bea294ba2e 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -9,7 +9,7 @@ "Channels": "Kanaler", "ChapterNameValue": "Kapitel {0}", "Collections": "Samlingar", - "DeviceOfflineWithName": "{0} har kopplat från", + "DeviceOfflineWithName": "{0} har kopplat ner", "DeviceOnlineWithName": "{0} är ansluten", "FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}", "Favorites": "Favoriter", From 51dd3f1e19c3ed77e2bba2aaecdd743ee627bd09 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 17 Oct 2020 16:01:36 +0200 Subject: [PATCH 095/203] Minor improvements --- .../AppBase/BaseApplicationPaths.cs | 2 +- Emby.Server.Implementations/ApplicationHost.cs | 14 +++++++------- .../ServerApplicationPaths.cs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 2adc1d6c34..660bbb2deb 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.AppBase } /// - public string VirtualDataPath { get; } = "%AppDataPath%"; + public string VirtualDataPath => "%AppDataPath%"; /// /// Gets the image cache path. diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0c8b0339bd..4110b6f3f7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -259,8 +259,8 @@ namespace Emby.Server.Implementations IServiceCollection serviceCollection) { _xmlSerializer = new MyXmlSerializer(); - _jsonSerializer = new JsonSerializer(); - + _jsonSerializer = new JsonSerializer(); + ServiceCollection = serviceCollection; _networkManager = networkManager; @@ -340,7 +340,7 @@ namespace Emby.Server.Implementations /// Gets the email address for use within a comment section of a user agent field. /// Presently used to provide contact information to MusicBrainz service. /// - public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org"; + public string ApplicationUserAgentAddress => "team@jellyfin.org"; /// /// Gets the current application name. @@ -404,7 +404,7 @@ namespace Emby.Server.Implementations /// /// Resolves this instance. /// - /// The type + /// The type. /// ``0. public T Resolve() => ServiceProvider.GetService(); @@ -1090,7 +1090,7 @@ namespace Emby.Server.Implementations { // No metafile, so lets see if the folder is versioned. metafile = dir.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)[^1]; - + int versionIndex = dir.LastIndexOf('_'); if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version ver)) { @@ -1099,9 +1099,9 @@ namespace Emby.Server.Implementations } else { - // Un-versioned folder - Add it under the path name and version 0.0.0.1. + // Un-versioned folder - Add it under the path name and version 0.0.0.1. versions.Add((new Version(0, 0, 0, 1), metafile, dir)); - } + } } } catch diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index dfdd4200e0..ac589b03cf 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -104,6 +104,6 @@ namespace Emby.Server.Implementations public string InternalMetadataPath { get; set; } /// - public string VirtualInternalMetadataPath { get; } = "%MetadataPath%"; + public string VirtualInternalMetadataPath => "%MetadataPath%"; } } From 49569ca0a0bf5534301e4e51bc263c73cc275a73 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 17 Oct 2020 16:19:57 +0200 Subject: [PATCH 096/203] Use nameof where possible --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 32 ++++++++--------- .../Channels/ChannelManager.cs | 2 +- .../Channels/ChannelPostScanTask.cs | 2 +- .../Data/SqliteItemRepository.cs | 34 +++++++++---------- Emby.Server.Implementations/Dto/DtoService.cs | 2 +- .../Images/ArtistImageProvider.cs | 2 +- .../Images/GenreImageProvider.cs | 9 +++-- .../Library/MusicManager.cs | 4 +-- .../Library/SearchEngine.cs | 28 +++++++-------- .../Library/Validators/ArtistsValidator.cs | 2 +- .../Library/Validators/PeopleValidator.cs | 2 +- .../Library/Validators/StudiosValidator.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 14 ++++---- .../LiveTv/LiveTvDtoService.cs | 8 ++--- .../LiveTv/LiveTvManager.cs | 22 ++++++------ .../ScheduledTasks/ScheduledTaskWorker.cs | 8 ++--- .../TV/TVSeriesManager.cs | 4 +-- Jellyfin.Api/Controllers/MoviesController.cs | 4 +-- .../Entities/Audio/MusicArtist.cs | 2 +- .../Entities/Audio/MusicGenre.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 4 +-- MediaBrowser.Controller/Entities/Genre.cs | 8 ++++- MediaBrowser.Controller/Entities/TV/Series.cs | 10 +++--- .../Entities/UserViewBuilder.cs | 30 ++++++++-------- MediaBrowser.Controller/Playlists/Playlist.cs | 4 +-- .../TheTvdb/TvdbPersonImageProvider.cs | 2 +- 26 files changed, 127 insertions(+), 116 deletions(-) diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 4b108b89ea..b805cd45c1 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -487,7 +487,7 @@ namespace Emby.Dlna.ContentDirectory User = user, Recursive = true, IsMissing = false, - ExcludeItemTypes = new[] { typeof(Book).Name }, + ExcludeItemTypes = new[] { nameof(Book) }, IsFolder = isFolder, MediaTypes = mediaTypes, DtoOptions = GetDtoOptions() @@ -556,7 +556,7 @@ namespace Emby.Dlna.ContentDirectory Limit = limit, StartIndex = startIndex, IsVirtualItem = false, - ExcludeItemTypes = new[] { typeof(Book).Name }, + ExcludeItemTypes = new[] { nameof(Book) }, IsPlaceHolder = false, DtoOptions = GetDtoOptions() }; @@ -575,7 +575,7 @@ namespace Emby.Dlna.ContentDirectory StartIndex = startIndex, Limit = limit, }; - query.IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }; + query.IncludeItemTypes = new[] { nameof(LiveTvChannel) }; SetSorting(query, sort, false); @@ -910,7 +910,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); - query.IncludeItemTypes = new[] { typeof(Series).Name }; + query.IncludeItemTypes = new[] { nameof(Series) }; var result = _libraryManager.GetItemsResult(query); @@ -923,7 +923,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); - query.IncludeItemTypes = new[] { typeof(Movie).Name }; + query.IncludeItemTypes = new[] { nameof(Movie) }; var result = _libraryManager.GetItemsResult(query); @@ -936,7 +936,7 @@ namespace Emby.Dlna.ContentDirectory // query.Parent = parent; query.SetUser(user); - query.IncludeItemTypes = new[] { typeof(BoxSet).Name }; + query.IncludeItemTypes = new[] { nameof(BoxSet) }; var result = _libraryManager.GetItemsResult(query); @@ -949,7 +949,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); - query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name }; + query.IncludeItemTypes = new[] { nameof(MusicAlbum) }; var result = _libraryManager.GetItemsResult(query); @@ -962,7 +962,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); - query.IncludeItemTypes = new[] { typeof(Audio).Name }; + query.IncludeItemTypes = new[] { nameof(Audio) }; var result = _libraryManager.GetItemsResult(query); @@ -975,7 +975,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); query.IsFavorite = true; - query.IncludeItemTypes = new[] { typeof(Audio).Name }; + query.IncludeItemTypes = new[] { nameof(Audio) }; var result = _libraryManager.GetItemsResult(query); @@ -988,7 +988,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); query.IsFavorite = true; - query.IncludeItemTypes = new[] { typeof(Series).Name }; + query.IncludeItemTypes = new[] { }; var result = _libraryManager.GetItemsResult(query); @@ -1001,7 +1001,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); query.IsFavorite = true; - query.IncludeItemTypes = new[] { typeof(Episode).Name }; + query.IncludeItemTypes = new[] { }; var result = _libraryManager.GetItemsResult(query); @@ -1014,7 +1014,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); query.IsFavorite = true; - query.IncludeItemTypes = new[] { typeof(Movie).Name }; + query.IncludeItemTypes = new[] { }; var result = _libraryManager.GetItemsResult(query); @@ -1027,7 +1027,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); query.IsFavorite = true; - query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name }; + query.IncludeItemTypes = new[] { }; var result = _libraryManager.GetItemsResult(query); @@ -1181,7 +1181,7 @@ namespace Emby.Dlna.ContentDirectory { UserId = user.Id, Limit = 50, - IncludeItemTypes = new[] { typeof(Episode).Name }, + IncludeItemTypes = new[] { }, ParentId = parent == null ? Guid.Empty : parent.Id, GroupItems = false }, @@ -1215,7 +1215,7 @@ namespace Emby.Dlna.ContentDirectory Recursive = true, ParentId = parentId, ArtistIds = new[] { item.Id }, - IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + IncludeItemTypes = new[] { }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions() @@ -1259,7 +1259,7 @@ namespace Emby.Dlna.ContentDirectory Recursive = true, ParentId = parentId, GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + IncludeItemTypes = new[] { }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions() diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index fb1bb65a09..db44bf4898 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -543,7 +543,7 @@ namespace Emby.Server.Implementations.Channels return _libraryManager.GetItemIds( new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(Channel).Name }, + IncludeItemTypes = new[] { nameof(Channel) }, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray(); } diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index eeb49b8fef..2391eed428 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.Channels var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(Channel).Name }, + IncludeItemTypes = new[] { nameof(Channel) }, ExcludeItemIds = installedChannelIds.ToArray() }); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index d09f84e174..56f0329353 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3908,7 +3908,7 @@ namespace Emby.Server.Implementations.Data if (query.IsPlayed.HasValue) { // We should probably figure this out for all folders, but for right now, this is the only place where we need it - if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], typeof(Series).Name, StringComparison.OrdinalIgnoreCase)) + if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(Series), StringComparison.OrdinalIgnoreCase)) { if (query.IsPlayed.Value) { @@ -4749,29 +4749,29 @@ namespace Emby.Server.Implementations.Data { var list = new List(); - if (IsTypeInQuery(typeof(Person).Name, query)) + if (IsTypeInQuery(nameof(Person), query)) { - list.Add(typeof(Person).Name); + list.Add(nameof(Person)); } - if (IsTypeInQuery(typeof(Genre).Name, query)) + if (IsTypeInQuery(nameof(Genre), query)) { - list.Add(typeof(Genre).Name); + list.Add(nameof(Genre)); } - if (IsTypeInQuery(typeof(MusicGenre).Name, query)) + if (IsTypeInQuery(nameof(MusicGenre), query)) { - list.Add(typeof(MusicGenre).Name); + list.Add(nameof(MusicGenre)); } - if (IsTypeInQuery(typeof(MusicArtist).Name, query)) + if (IsTypeInQuery(nameof(MusicArtist), query)) { - list.Add(typeof(MusicArtist).Name); + list.Add(nameof(MusicArtist)); } - if (IsTypeInQuery(typeof(Studio).Name, query)) + if (IsTypeInQuery(nameof(Studio), query)) { - list.Add(typeof(Studio).Name); + list.Add(nameof(Studio)); } return list; @@ -4826,12 +4826,12 @@ namespace Emby.Server.Implementations.Data var types = new[] { - typeof(Episode).Name, - typeof(Video).Name, - typeof(Movie).Name, - typeof(MusicVideo).Name, - typeof(Series).Name, - typeof(Season).Name + nameof(Episode), + nameof(Video), + nameof(Movie), + nameof(MusicVideo), + nameof(Series), + nameof(Season) }; if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase))) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index edb8753fd4..73502c2c9f 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -465,7 +465,7 @@ namespace Emby.Server.Implementations.Dto { var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + IncludeItemTypes = new[] { nameof(MusicAlbum) }, Name = item.Album, Limit = 1 }); diff --git a/Emby.Server.Implementations/Images/ArtistImageProvider.cs b/Emby.Server.Implementations/Images/ArtistImageProvider.cs index bf57382ed4..afa4ec7b1b 100644 --- a/Emby.Server.Implementations/Images/ArtistImageProvider.cs +++ b/Emby.Server.Implementations/Images/ArtistImageProvider.cs @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Images // return _libraryManager.GetItemList(new InternalItemsQuery // { // ArtistIds = new[] { item.Id }, - // IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + // IncludeItemTypes = new[] { nameof(MusicAlbum) }, // OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, // Limit = 4, // Recursive = true, diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index 1cd4cd66bd..3817882312 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -42,7 +42,12 @@ namespace Emby.Server.Implementations.Images return _libraryManager.GetItemList(new InternalItemsQuery { Genres = new[] { item.Name }, - IncludeItemTypes = new[] { typeof(MusicAlbum).Name, typeof(MusicVideo).Name, typeof(Audio).Name }, + IncludeItemTypes = new[] + { + nameof(MusicAlbum), + nameof(MusicVideo), + nameof(Audio) + }, OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, @@ -77,7 +82,7 @@ namespace Emby.Server.Implementations.Images return _libraryManager.GetItemList(new InternalItemsQuery { Genres = new[] { item.Name }, - IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name }, + IncludeItemTypes = new[] { nameof(Series), nameof(Movie) }, OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 877fdec86e..658c53f288 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Library var genres = item .GetRecursiveChildren(user, new InternalItemsQuery(user) { - IncludeItemTypes = new[] { typeof(Audio).Name }, + IncludeItemTypes = new[] { nameof(Audio) }, DtoOptions = dtoOptions }) .Cast public const string Token = "Jellyfin-Token"; + + /// + /// Is Api Key. + /// + public const string IsApiKey = "Jellyfin-IsApiKey"; } } diff --git a/Jellyfin.Api/Helpers/ClaimHelpers.cs b/Jellyfin.Api/Helpers/ClaimHelpers.cs index df235ced25..29e6b4193e 100644 --- a/Jellyfin.Api/Helpers/ClaimHelpers.cs +++ b/Jellyfin.Api/Helpers/ClaimHelpers.cs @@ -63,6 +63,19 @@ namespace Jellyfin.Api.Helpers public static string? GetToken(in ClaimsPrincipal user) => GetClaimValue(user, InternalClaimTypes.Token); + /// + /// Gets a flag specifying whether the request is using an api key. + /// + /// Current claims principal. + /// The flag specifying whether the request is using an api key. + public static bool GetIsApiKey(in ClaimsPrincipal user) + { + var claimValue = GetClaimValue(user, InternalClaimTypes.IsApiKey); + return !string.IsNullOrEmpty(claimValue) + && bool.TryParse(claimValue, out var parsedClaimValue) + && parsedClaimValue; + } + private static string? GetClaimValue(in ClaimsPrincipal user, string name) { return user?.Identities diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index 735c46ef86..5c642edff2 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -1,10 +1,11 @@ -#pragma warning disable CS1591 - using System; using Jellyfin.Data.Entities; namespace MediaBrowser.Controller.Net { + /// + /// The request authorization info. + /// public class AuthorizationInfo { /// @@ -43,6 +44,14 @@ namespace MediaBrowser.Controller.Net /// The token. public string Token { get; set; } + /// + /// Gets or sets a value indicating whether the authorization is from an api key. + /// + public bool IsApiKey { get; set; } + + /// + /// Gets or sets the user making the request. + /// public User User { get; set; } } } From 8b83e4e972243db618972e33705b959bf98ca9f4 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 28 Oct 2020 17:57:16 -0600 Subject: [PATCH 115/203] Fix tests --- .../Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs index 4ea5094b66..33534abd2f 100644 --- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs @@ -128,6 +128,7 @@ namespace Jellyfin.Api.Tests.Auth var authorizationInfo = _fixture.Create(); authorizationInfo.User = _fixture.Create(); authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin); + authorizationInfo.IsApiKey = false; _jellyfinAuthServiceMock.Setup( a => a.Authenticate( From c0383ba07d275daf3241c68e0aad5f493cff44cb Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 28 Oct 2020 20:23:39 -0600 Subject: [PATCH 116/203] Add missing slashes in ffmpeg argument. --- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 2afa878f41..c41116fa71 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers var baseUrlParam = string.Format( CultureInfo.InvariantCulture, - "\"hls{0}\"", + "\"hls/{0}\"/", Path.GetFileNameWithoutExtension(outputPath)); return string.Format( From 96071ef30d422001d474c749502f44e1ef7641ff Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Wed, 28 Oct 2020 20:26:33 -0600 Subject: [PATCH 117/203] Move slash to correct location for quoting --- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index c41116fa71..d7bcf79c1f 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers var baseUrlParam = string.Format( CultureInfo.InvariantCulture, - "\"hls/{0}\"/", + "\"hls/{0}/\"", Path.GetFileNameWithoutExtension(outputPath)); return string.Format( From 372f0eb38a9c0803d69d5dc3c4f0fdedd948cd0c Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 29 Oct 2020 11:17:13 -0600 Subject: [PATCH 118/203] Remove AddItemFields --- Jellyfin.Api/Controllers/ArtistsController.cs | 6 ++---- .../Controllers/ChannelsController.cs | 8 +++---- Jellyfin.Api/Controllers/GenresController.cs | 3 +-- .../Controllers/InstantMixController.cs | 21 +++++++------------ Jellyfin.Api/Controllers/ItemsController.cs | 6 ++---- Jellyfin.Api/Controllers/LibraryController.cs | 3 +-- Jellyfin.Api/Controllers/LiveTvController.cs | 15 +++++-------- Jellyfin.Api/Controllers/MoviesController.cs | 3 +-- .../Controllers/MusicGenresController.cs | 3 +-- Jellyfin.Api/Controllers/PersonsController.cs | 3 +-- .../Controllers/PlaylistsController.cs | 3 +-- Jellyfin.Api/Controllers/StudiosController.cs | 3 +-- Jellyfin.Api/Controllers/TvShowsController.cs | 12 ++++------- .../Controllers/UserLibraryController.cs | 3 +-- Jellyfin.Api/Controllers/YearsController.cs | 3 +-- Jellyfin.Api/Extensions/DtoExtensions.cs | 16 -------------- 16 files changed, 32 insertions(+), 79 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index c39f442a20..ff1d528474 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -113,8 +113,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -322,8 +321,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index e3b9194694..838fec0776 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -133,11 +133,10 @@ namespace Jellyfin.Api.Controllers { Limit = limit, StartIndex = startIndex, - ChannelIds = new[] { channelId }, + ChannelIds = new[] {channelId}, ParentId = folderId ?? Guid.Empty, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), - DtoOptions = new DtoOptions() - .AddItemFields(fields) + DtoOptions = new DtoOptions { Fields = fields } }; foreach (var filter in filters) @@ -213,8 +212,7 @@ namespace Jellyfin.Api.Controllers .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(i => new Guid(i)) .ToArray(), - DtoOptions = new DtoOptions() - .AddItemFields(fields) + DtoOptions = new DtoOptions{ Fields = fields } }; foreach (var filter in filters) diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 9e71e82a33..da774f7f18 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -114,8 +114,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index b4a4c35754..f40df0a44d 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -78,8 +78,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); @@ -115,8 +114,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions); @@ -152,8 +150,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions); @@ -188,8 +185,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions); @@ -225,8 +221,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); @@ -262,8 +257,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); @@ -299,8 +293,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 43f34abbf9..4543888b21 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -233,8 +233,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -544,8 +543,7 @@ namespace Jellyfin.Api.Controllers { var user = _userManager.GetUserById(userId); var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId); - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 865b0010d8..4fbf2a4b45 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -892,8 +892,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request); var query = new InternalItemsQuery(user) diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 6c1da075dd..0a5ee2f4d1 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -154,8 +154,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool enableFavoriteSorting = false, [FromQuery] bool addCurrentProgram = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -274,8 +273,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isLibraryItem, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -606,8 +604,7 @@ namespace Jellyfin.Api.Controllers } } - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); @@ -662,8 +659,7 @@ namespace Jellyfin.Api.Controllers } } - var dtoOptions = new DtoOptions() - .AddItemFields(body.Fields) + var dtoOptions = new DtoOptions{ Fields = body.Fields } .AddClientFields(Request) .AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes); return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); @@ -729,8 +725,7 @@ namespace Jellyfin.Api.Controllers GenreIds = RequestHelpers.GetGuids(genreIds) }; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); return _liveTvManager.GetRecommendedPrograms(query, dtoOptions, CancellationToken.None); diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index bf8279e0c7..10c570893c 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -72,8 +72,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request); var categories = new List(); diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 61d6aaf605..b3589442ef 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -113,8 +113,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 4b367f276c..55f6f5a4c3 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -113,8 +113,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index ecb0e37830..3242896ed8 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -176,8 +176,7 @@ namespace Jellyfin.Api.Controllers items = items.Take(limit.Value).ToArray(); } - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index 987fe71950..47b4ce01a3 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -112,8 +112,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 1f68b5d9e3..c53201f1e7 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -82,8 +82,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { - var options = new DtoOptions() - .AddItemFields(fields) + var options = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!); @@ -146,8 +145,7 @@ namespace Jellyfin.Api.Controllers var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId); - var options = new DtoOptions() - .AddItemFields(fields!) + var options = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!); @@ -217,8 +215,7 @@ namespace Jellyfin.Api.Controllers List episodes; - var dtoOptions = new DtoOptions() - .AddItemFields(fields!) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); @@ -345,8 +342,7 @@ namespace Jellyfin.Api.Controllers AdjacentTo = adjacentTo }); - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 3857ee08a7..d412933eac 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -287,8 +287,7 @@ namespace Jellyfin.Api.Controllers } } - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 0438f49c08..3a5840559f 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -83,8 +83,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool recursive = true, [FromQuery] bool? enableImages = true) { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions{ Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs index a886760c64..6dee9db380 100644 --- a/Jellyfin.Api/Extensions/DtoExtensions.cs +++ b/Jellyfin.Api/Extensions/DtoExtensions.cs @@ -13,22 +13,6 @@ namespace Jellyfin.Api.Extensions /// public static class DtoExtensions { - /// - /// Add Dto Item fields. - /// - /// - /// Converted from IHasItemFields. - /// Legacy order: 1. - /// - /// DtoOptions object. - /// Array of item fields. - /// Modified DtoOptions object. - internal static DtoOptions AddItemFields(this DtoOptions dtoOptions, ItemFields[] fields) - { - dtoOptions.Fields = fields; - return dtoOptions; - } - /// /// Add additional fields depending on client. /// From 42c2ab97e7292cb8b61ec5dce2b2201450756d81 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 29 Oct 2020 11:32:02 -0600 Subject: [PATCH 119/203] Support IReadOnlyList for JsonCommaDelimitedArrayConverter --- ...JsonCommaDelimitedArrayConverterFactory.cs | 4 +- .../Json/JsonCommaDelimitedArrayTests.cs | 26 +++--- .../JsonCommaDelimitedIReadOnlyListTests.cs | 92 +++++++++++++++++++ ...cBodyModel.cs => GenericBodyArrayModel.cs} | 4 +- .../Models/GenericBodyIReadOnlyListModel.cs | 19 ++++ 5 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs rename tests/Jellyfin.Common.Tests/Models/{GenericBodyModel.cs => GenericBodyArrayModel.cs} (93%) create mode 100644 tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs index b7b1daf76a..24ed3ea19e 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs @@ -21,8 +21,8 @@ namespace MediaBrowser.Common.Json.Converters /// public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { - var structType = typeToConvert.GetElementType(); + var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; return (JsonConverter)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType)); } } -} \ No newline at end of file +} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs index 16f8a690e6..0d2bdd1af9 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs @@ -11,82 +11,82 @@ namespace Jellyfin.Common.Tests.Json [Fact] public static void Deserialize_String_Valid_Success() { - var desiredValue = new GenericBodyModel + var desiredValue = new GenericBodyArrayModel { Value = new[] { "a", "b", "c" } }; var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", options); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", options); Assert.Equal(desiredValue.Value, value?.Value); } [Fact] public static void Deserialize_String_Space_Valid_Success() { - var desiredValue = new GenericBodyModel + var desiredValue = new GenericBodyArrayModel { Value = new[] { "a", "b", "c" } }; var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a, b, c"" }", options); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a, b, c"" }", options); Assert.Equal(desiredValue.Value, value?.Value); } [Fact] public static void Deserialize_GenericCommandType_Valid_Success() { - var desiredValue = new GenericBodyModel + var desiredValue = new GenericBodyArrayModel { Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } }; var options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options); Assert.Equal(desiredValue.Value, value?.Value); } [Fact] public static void Deserialize_GenericCommandType_Space_Valid_Success() { - var desiredValue = new GenericBodyModel + var desiredValue = new GenericBodyArrayModel { Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } }; var options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options); Assert.Equal(desiredValue.Value, value?.Value); } [Fact] public static void Deserialize_String_Array_Valid_Success() { - var desiredValue = new GenericBodyModel + var desiredValue = new GenericBodyArrayModel { Value = new[] { "a", "b", "c" } }; var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""a"",""b"",""c""] }", options); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""a"",""b"",""c""] }", options); Assert.Equal(desiredValue.Value, value?.Value); } [Fact] public static void Deserialize_GenericCommandType_Array_Valid_Success() { - var desiredValue = new GenericBodyModel + var desiredValue = new GenericBodyArrayModel { Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } }; var options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options); Assert.Equal(desiredValue.Value, value?.Value); } } -} \ No newline at end of file +} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs new file mode 100644 index 0000000000..34ad9bac79 --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs @@ -0,0 +1,92 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Jellyfin.Common.Tests.Models; +using MediaBrowser.Model.Session; +using Xunit; + +namespace Jellyfin.Common.Tests.Json +{ + public static class JsonCommaDelimitedIReadOnlyListTests + { + [Fact] + public static void Deserialize_String_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_String_Space_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a, b, c"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Space_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_String_Array_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""a"",""b"",""c""] }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Array_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + } +} diff --git a/tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs b/tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs similarity index 93% rename from tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs rename to tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs index 9a6c382fea..276e1bfbe2 100644 --- a/tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs +++ b/tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs @@ -8,7 +8,7 @@ namespace Jellyfin.Common.Tests.Models /// The generic body model. /// /// The value type. - public class GenericBodyModel + public class GenericBodyArrayModel { /// /// Gets or sets the value. @@ -17,4 +17,4 @@ namespace Jellyfin.Common.Tests.Models [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] public T[] Value { get; set; } = default!; } -} \ No newline at end of file +} diff --git a/tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs b/tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs new file mode 100644 index 0000000000..627454b25f --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using MediaBrowser.Common.Json.Converters; + +namespace Jellyfin.Common.Tests.Models +{ + /// + /// The generic body IReadOnlyList model. + /// + /// The value type. + public class GenericBodyIReadOnlyListModel + { + /// + /// Gets or sets the value. + /// + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList Value { get; set; } = default!; + } +} From 11d69fd3b1c25927c1046c4a095a3bd9f29fa722 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 29 Oct 2020 11:36:45 -0600 Subject: [PATCH 120/203] Make MrLinter happy --- Jellyfin.Api/Controllers/ArtistsController.cs | 4 ++-- Jellyfin.Api/Controllers/ChannelsController.cs | 4 ++-- Jellyfin.Api/Controllers/GenresController.cs | 2 +- Jellyfin.Api/Controllers/InstantMixController.cs | 14 +++++++------- Jellyfin.Api/Controllers/ItemsController.cs | 4 ++-- Jellyfin.Api/Controllers/LibraryController.cs | 2 +- Jellyfin.Api/Controllers/LiveTvController.cs | 10 +++++----- Jellyfin.Api/Controllers/MoviesController.cs | 2 +- Jellyfin.Api/Controllers/MusicGenresController.cs | 2 +- Jellyfin.Api/Controllers/PersonsController.cs | 2 +- Jellyfin.Api/Controllers/PlaylistsController.cs | 2 +- Jellyfin.Api/Controllers/StudiosController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 8 ++++---- Jellyfin.Api/Controllers/UserLibraryController.cs | 2 +- Jellyfin.Api/Controllers/YearsController.cs | 2 +- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index ff1d528474..6d5d612cb3 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -321,7 +321,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index 838fec0776..c98c0fdd0d 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers { Limit = limit, StartIndex = startIndex, - ChannelIds = new[] {channelId}, + ChannelIds = new[] { channelId }, ParentId = folderId ?? Guid.Empty, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), DtoOptions = new DtoOptions { Fields = fields } @@ -212,7 +212,7 @@ namespace Jellyfin.Api.Controllers .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(i => new Guid(i)) .ToArray(), - DtoOptions = new DtoOptions{ Fields = fields } + DtoOptions = new DtoOptions { Fields = fields } }; foreach (var filter in filters) diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index da774f7f18..57ec02c1ca 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index f40df0a44d..7591c1b091 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -78,7 +78,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); @@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions); @@ -150,7 +150,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions); @@ -185,7 +185,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions); @@ -221,7 +221,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); @@ -257,7 +257,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); @@ -293,7 +293,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 4543888b21..4c91652912 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -233,7 +233,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -543,7 +543,7 @@ namespace Jellyfin.Api.Controllers { var user = _userManager.GetUserById(userId); var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId); - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 4fbf2a4b45..e0f0bda26f 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -892,7 +892,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request); var query = new InternalItemsQuery(user) diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 0a5ee2f4d1..600cf0a299 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool enableFavoriteSorting = false, [FromQuery] bool addCurrentProgram = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -273,7 +273,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isLibraryItem, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -604,7 +604,7 @@ namespace Jellyfin.Api.Controllers } } - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); @@ -659,7 +659,7 @@ namespace Jellyfin.Api.Controllers } } - var dtoOptions = new DtoOptions{ Fields = body.Fields } + var dtoOptions = new DtoOptions { Fields = body.Fields } .AddClientFields(Request) .AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes); return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); @@ -725,7 +725,7 @@ namespace Jellyfin.Api.Controllers GenreIds = RequestHelpers.GetGuids(genreIds) }; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); return _liveTvManager.GetRecommendedPrograms(query, dtoOptions, CancellationToken.None); diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index 10c570893c..f041946088 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request); var categories = new List(); diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index b3589442ef..fde4faf773 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 55f6f5a4c3..8ffc47f0ac 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 3242896ed8..a1c8219ca9 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -176,7 +176,7 @@ namespace Jellyfin.Api.Controllers items = items.Take(limit.Value).ToArray(); } - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index 47b4ce01a3..a9cba9dba5 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index c53201f1e7..e07e906001 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { - var options = new DtoOptions{ Fields = fields } + var options = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!); @@ -145,7 +145,7 @@ namespace Jellyfin.Api.Controllers var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId); - var options = new DtoOptions{ Fields = fields } + var options = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!); @@ -215,7 +215,7 @@ namespace Jellyfin.Api.Controllers List episodes; - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); @@ -342,7 +342,7 @@ namespace Jellyfin.Api.Controllers AdjacentTo = adjacentTo }); - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index d412933eac..ff68cd497b 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -287,7 +287,7 @@ namespace Jellyfin.Api.Controllers } } - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 3a5840559f..0b4a2776b2 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -83,7 +83,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool recursive = true, [FromQuery] bool? enableImages = true) { - var dtoOptions = new DtoOptions{ Fields = fields } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); From 2c6b31fc7bf45808ca041e9e76046495b7d351d6 Mon Sep 17 00:00:00 2001 From: Oatavandi Date: Thu, 29 Oct 2020 18:39:38 +0000 Subject: [PATCH 121/203] Translated using Weblate (Tamil) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/ --- Emby.Server.Implementations/Localization/Core/ta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index ae38f45e1c..8089fc3042 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -101,7 +101,7 @@ "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது", "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன", "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.", - "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.", + "TaskCleanTranscodeDescription": "ஒரு நாளைக்கு மேற்பட்ட பழைய டிரான்ஸ்கோட் கோப்புகளை நீக்குகிறது.", "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.", "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.", "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.", From 429e59fb818e605339eab2b368ceaaf2a1bd5c2b Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 29 Oct 2020 13:55:57 -0600 Subject: [PATCH 122/203] Fix null reference --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 996b1b5c1e..1d733479cc 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3084,7 +3084,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var whichCodec = videoStream.Codec.ToLowerInvariant(); + var whichCodec = videoStream.Codec?.ToLowerInvariant(); switch (whichCodec) { case "avc": From 3568c5f39b7429544c8a26677f400cfee2eaa7fd Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 29 Oct 2020 13:56:29 -0600 Subject: [PATCH 123/203] Fix early filestream close --- .../LiveTv/TunerHosts/SharedHttpStream.cs | 2 +- Jellyfin.Api/Controllers/LiveTvController.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 6c10fca8c6..29ab1cd40b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var typeName = GetType().Name; Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None) .ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 22f140ea62..2cc2f0e74d 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1220,11 +1220,11 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - await using var memoryStream = new MemoryStream(); await new ProgressiveFileCopier(liveStreamInfo, null, _transcodingJobHelper, CancellationToken.None) - .WriteToAsync(memoryStream, CancellationToken.None) + .WriteToAsync(Response.Body, CancellationToken.None) .ConfigureAwait(false); - return File(memoryStream, MimeTypes.GetMimeType("file." + container)); + Response.ContentType = MimeTypes.GetMimeType("file." + container); + return Ok(); } private void AssertUserCanManageLiveTv() From 0c674b496f3f4503b6d45c763aadc9b1c5d2735d Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 29 Oct 2020 13:58:47 -0600 Subject: [PATCH 124/203] Add stream disposal comment. --- .../LiveTv/TunerHosts/SharedHttpStream.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 29ab1cd40b..10e5eab73c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var typeName = GetType().Name; Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url); + // Response stream is disposed manually. var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None) .ConfigureAwait(false); From ef43878bbc204a169adb0ad0639cc143cfd2db11 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 29 Oct 2020 16:11:40 -0600 Subject: [PATCH 125/203] Support IReadOnlyList in CommaDelimitedArrayModelBinder --- Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs index 208566dc8e..4f012cab20 100644 --- a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Api.ModelBinders public Task BindModelAsync(ModelBindingContext bindingContext) { var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); - var elementType = bindingContext.ModelType.GetElementType(); + var elementType = bindingContext.ModelType.GetElementType() ?? bindingContext.ModelType.GenericTypeArguments[0]; var converter = TypeDescriptor.GetConverter(elementType); if (valueProviderResult.Length > 1) From d887e424052e3f57b7578d9dc7f62bcd8085fcbe Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 29 Oct 2020 19:16:39 -0400 Subject: [PATCH 126/203] Fix possible NullReferenceException --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 437833aa38..5f25c7737a 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -460,7 +460,7 @@ namespace Jellyfin.Server.Implementations.Users // the authentication provider might have created it user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); - if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user != null) { UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy()); From 72263613d039ccfb70b70bae9f53da53bc8757c4 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 29 Oct 2020 20:30:33 -0400 Subject: [PATCH 127/203] Convert some code in UserManager to async --- Jellyfin.Api/Controllers/ImageController.cs | 6 ++--- Jellyfin.Api/Controllers/UserController.cs | 18 ++++++--------- .../Users/UserManager.cs | 22 +++++++++---------- .../Library/IUserManager.cs | 9 +++++--- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 05efe23553..4a67c1aede 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -109,7 +109,7 @@ namespace Jellyfin.Api.Controllers var userDataPath = Path.Combine(_serverConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username); if (user.ProfileImage != null) { - _userManager.ClearProfileImage(user); + await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); } user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType))); @@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public ActionResult DeleteUserImage( + public async Task DeleteUserImage( [FromRoute, Required] Guid userId, [FromRoute, Required] ImageType imageType, [FromRoute] int? index = null) @@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers _logger.LogError(e, "Error deleting user profile image:"); } - _userManager.ClearProfileImage(user); + await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 50bb8bb2aa..7b0897bfb9 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -381,17 +381,13 @@ namespace Jellyfin.Api.Controllers var user = _userManager.GetUserById(userId); - if (string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal)) - { - await _userManager.UpdateUserAsync(user).ConfigureAwait(false); - _userManager.UpdateConfiguration(user.Id, updateUser.Configuration); - } - else + if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal)) { await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false); - _userManager.UpdateConfiguration(updateUser.Id, updateUser.Configuration); } + await _userManager.UpdateConfigurationAsync(user.Id, updateUser.Configuration).ConfigureAwait(false); + return NoContent(); } @@ -409,7 +405,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public ActionResult UpdateUserPolicy( + public async Task UpdateUserPolicy( [FromRoute, Required] Guid userId, [FromBody] UserPolicy newPolicy) { @@ -447,7 +443,7 @@ namespace Jellyfin.Api.Controllers _sessionManager.RevokeUserTokens(user.Id, currentToken); } - _userManager.UpdatePolicy(userId, newPolicy); + await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false); return NoContent(); } @@ -464,7 +460,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public ActionResult UpdateUserConfiguration( + public async Task UpdateUserConfiguration( [FromRoute, Required] Guid userId, [FromBody] UserConfiguration userConfig) { @@ -473,7 +469,7 @@ namespace Jellyfin.Api.Controllers return Forbid("User configuration update not allowed"); } - _userManager.UpdateConfiguration(userId, userConfig); + await _userManager.UpdateConfigurationAsync(userId, userConfig).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index b41a5ee5ca..40b89ed284 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -434,9 +434,7 @@ namespace Jellyfin.Server.Implementations.Users if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user != null) { - UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy()); - - await UpdateUserAsync(user).ConfigureAwait(false); + await UpdatePolicyAsync(user.Id, hasNewUserPolicy.GetNewUserPolicy()).ConfigureAwait(false); } } } @@ -615,9 +613,9 @@ namespace Jellyfin.Server.Implementations.Users } /// - public void UpdateConfiguration(Guid userId, UserConfiguration config) + public async Task UpdateConfigurationAsync(Guid userId, UserConfiguration config) { - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); var user = dbContext.Users .Include(u => u.Permissions) .Include(u => u.Preferences) @@ -644,13 +642,13 @@ namespace Jellyfin.Server.Implementations.Users user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); dbContext.Update(user); - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); } /// - public void UpdatePolicy(Guid userId, UserPolicy policy) + public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy) { - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); var user = dbContext.Users .Include(u => u.Permissions) .Include(u => u.Preferences) @@ -715,15 +713,15 @@ namespace Jellyfin.Server.Implementations.Users user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); dbContext.Update(user); - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); } /// - public void ClearProfileImage(User user) + public async Task ClearProfileImageAsync(User user) { - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); dbContext.Remove(user.ProfileImage); - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); user.ProfileImage = null; } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6a4f5cf679..8fd3b8c347 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -158,7 +158,8 @@ namespace MediaBrowser.Controller.Library /// /// The user's Id. /// The request containing the new user configuration. - void UpdateConfiguration(Guid userId, UserConfiguration config); + /// A task representing the update. + Task UpdateConfigurationAsync(Guid userId, UserConfiguration config); /// /// This method updates the user's policy. @@ -167,12 +168,14 @@ namespace MediaBrowser.Controller.Library /// /// The user's Id. /// The request containing the new user policy. - void UpdatePolicy(Guid userId, UserPolicy policy); + /// A task representing the update. + Task UpdatePolicyAsync(Guid userId, UserPolicy policy); /// /// Clears the user's profile image. /// /// The user. - void ClearProfileImage(User user); + /// A task representing the clearing of the profile image. + Task ClearProfileImageAsync(User user); } } From 00f0c14d7b953111e0bacc134b161c329b8e5fa1 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sat, 31 Oct 2020 16:09:22 +0800 Subject: [PATCH 128/203] respect music quality settings when transcoding --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 12 +++++++++--- Jellyfin.Api/Controllers/MediaInfoController.cs | 4 ++-- Jellyfin.Api/Controllers/UniversalAudioController.cs | 8 +++++--- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 4 ++-- MediaBrowser.Model/Dlna/AudioOptions.cs | 4 ++-- MediaBrowser.Model/Dlna/DeviceProfile.cs | 4 ++-- MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs | 2 +- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 1153a601e7..e07690e110 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -295,6 +295,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. + /// Optional. The maximum streaming bitrate. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. /// Optional. Specify a specific number of audio channels to encode to, e.g. 2. /// Optional. Specify a maximum number of audio channels to encode to, e.g. 2. @@ -351,6 +352,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? maxStreamingBitrate, [FromQuery] int? audioBitRate, [FromQuery] int? audioChannels, [FromQuery] int? maxAudioChannels, @@ -403,7 +405,7 @@ namespace Jellyfin.Api.Controllers BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, - AudioBitRate = audioBitRate, + AudioBitRate = audioBitRate ?? maxStreamingBitrate, MaxAudioBitDepth = maxAudioBitDepth, AudioChannels = audioChannels, Profile = profile, @@ -623,6 +625,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. + /// Optional. The maximum streaming bitrate. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. /// Optional. Specify a specific number of audio channels to encode to, e.g. 2. /// Optional. Specify a maximum number of audio channels to encode to, e.g. 2. @@ -677,6 +680,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? maxStreamingBitrate, [FromQuery] int? audioBitRate, [FromQuery] int? audioChannels, [FromQuery] int? maxAudioChannels, @@ -729,7 +733,7 @@ namespace Jellyfin.Api.Controllers BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, - AudioBitRate = audioBitRate, + AudioBitRate = audioBitRate ?? maxStreamingBitrate, MaxAudioBitDepth = maxAudioBitDepth, AudioChannels = audioChannels, Profile = profile, @@ -959,6 +963,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Whether to break on non key frames. /// Optional. Specify a specific audio sample rate, e.g. 44100. /// Optional. The maximum audio bit depth. + /// Optional. The maximum streaming bitrate. /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. /// Optional. Specify a specific number of audio channels to encode to, e.g. 2. /// Optional. Specify a maximum number of audio channels to encode to, e.g. 2. @@ -1017,6 +1022,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? breakOnNonKeyFrames, [FromQuery] int? audioSampleRate, [FromQuery] int? maxAudioBitDepth, + [FromQuery] int? maxStreamingBitrate, [FromQuery] int? audioBitRate, [FromQuery] int? audioChannels, [FromQuery] int? maxAudioChannels, @@ -1069,7 +1075,7 @@ namespace Jellyfin.Api.Controllers BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, AudioSampleRate = audioSampleRate, MaxAudioChannels = maxAudioChannels, - AudioBitRate = audioBitRate, + AudioBitRate = audioBitRate ?? maxStreamingBitrate, MaxAudioBitDepth = maxAudioBitDepth, AudioChannels = audioChannels, Profile = profile, diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index 4c21999b1b..186024585b 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -104,7 +104,7 @@ namespace Jellyfin.Api.Controllers public async Task> GetPostedPlaybackInfo( [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, - [FromQuery] long? maxStreamingBitrate, + [FromQuery] int? maxStreamingBitrate, [FromQuery] long? startTimeTicks, [FromQuery] int? audioStreamIndex, [FromQuery] int? subtitleStreamIndex, @@ -234,7 +234,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? openToken, [FromQuery] Guid? userId, [FromQuery] string? playSessionId, - [FromQuery] long? maxStreamingBitrate, + [FromQuery] int? maxStreamingBitrate, [FromQuery] long? startTimeTicks, [FromQuery] int? audioStreamIndex, [FromQuery] int? subtitleStreamIndex, diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index df20a92b3d..1cfdb9d8b4 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -76,6 +76,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of audio channels. /// Optional. The number of how many audio channels to transcode to. /// Optional. The maximum streaming bitrate. + /// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults. /// Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms. /// Optional. The container to transcode to. /// Optional. The transcoding protocol. @@ -104,7 +105,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? audioCodec, [FromQuery] int? maxAudioChannels, [FromQuery] int? transcodingAudioChannels, - [FromQuery] long? maxStreamingBitrate, + [FromQuery] int? maxStreamingBitrate, + [FromQuery] int? audioBitRate, [FromQuery] long? startTimeTicks, [FromQuery] string? transcodingContainer, [FromQuery] string? transcodingProtocol, @@ -212,7 +214,7 @@ namespace Jellyfin.Api.Controllers AudioSampleRate = maxAudioSampleRate, MaxAudioChannels = maxAudioChannels, MaxAudioBitDepth = maxAudioBitDepth, - AudioChannels = isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)), + AudioBitRate = audioBitRate ?? maxStreamingBitrate, StartTimeTicks = startTimeTicks, SubtitleMethod = SubtitleDeliveryMethod.Hls, RequireAvc = true, @@ -244,7 +246,7 @@ namespace Jellyfin.Api.Controllers BreakOnNonKeyFrames = breakOnNonKeyFrames, AudioSampleRate = maxAudioSampleRate, MaxAudioChannels = maxAudioChannels, - AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)), + AudioBitRate = isStatic ? (int?)null : (audioBitRate ?? maxStreamingBitrate), MaxAudioBitDepth = maxAudioBitDepth, AudioChannels = maxAudioChannels, CopyTimestamps = true, diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index e78f63b256..0d8315dee1 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -166,7 +166,7 @@ namespace Jellyfin.Api.Helpers MediaSourceInfo mediaSource, DeviceProfile profile, AuthorizationInfo auth, - long? maxBitrate, + int? maxBitrate, long startTimeTicks, string mediaSourceId, int? audioStreamIndex, @@ -551,7 +551,7 @@ namespace Jellyfin.Api.Helpers } } - private long? GetMaxBitrate(long? clientMaxBitrate, User user, string ipAddress) + private int? GetMaxBitrate(int? clientMaxBitrate, User user, string ipAddress) { var maxBitrate = clientMaxBitrate; var remoteClientMaxBitrate = user.RemoteClientBitrateLimit ?? 0; diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs index 67e4ffe03e..bbb8bf4263 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/AudioOptions.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Model.Dlna /// /// The application's configured quality setting. /// - public long? MaxBitrate { get; set; } + public int? MaxBitrate { get; set; } /// /// Gets or sets the context. @@ -67,7 +67,7 @@ namespace MediaBrowser.Model.Dlna /// Gets the maximum bitrate. /// /// System.Nullable<System.Int32>. - public long? GetMaxBitrate(bool isAudio) + public int? GetMaxBitrate(bool isAudio) { if (MaxBitrate.HasValue) { diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 44412f3e41..e842efead9 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -62,9 +62,9 @@ namespace MediaBrowser.Model.Dlna public int? MaxIconHeight { get; set; } - public long? MaxStreamingBitrate { get; set; } + public int? MaxStreamingBitrate { get; set; } - public long? MaxStaticBitrate { get; set; } + public int? MaxStaticBitrate { get; set; } public int? MusicStreamingTranscodingBitrate { get; set; } diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index 83bda5d569..a8ea405e28 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Model.MediaInfo public string PlaySessionId { get; set; } - public long? MaxStreamingBitrate { get; set; } + public int? MaxStreamingBitrate { get; set; } public long? StartTimeTicks { get; set; } From 7f8a73d8e9a7d07f2f528bcf41998bed4b130cca Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 31 Oct 2020 12:49:41 +0100 Subject: [PATCH 129/203] Http1AndHttp2 is the default, no need to explicitly enable it --- Jellyfin.Server/Program.cs | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 5573c04395..97a51c202b 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -290,23 +290,19 @@ namespace Jellyfin.Server { _logger.LogInformation("Kestrel listening on {IpAddress}", address); options.Listen(address, appHost.HttpPort); + if (appHost.ListenWithHttps) { - options.Listen(address, appHost.HttpsPort, listenOptions => - { - listenOptions.UseHttps(appHost.Certificate); - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); + options.Listen( + address, + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); } else if (builderContext.HostingEnvironment.IsDevelopment()) { try { - options.Listen(address, appHost.HttpsPort, listenOptions => - { - listenOptions.UseHttps(); - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); + options.Listen(address, appHost.HttpsPort, listenOptions => listenOptions.UseHttps()); } catch (InvalidOperationException ex) { @@ -322,21 +318,15 @@ namespace Jellyfin.Server if (appHost.ListenWithHttps) { - options.ListenAnyIP(appHost.HttpsPort, listenOptions => - { - listenOptions.UseHttps(appHost.Certificate); - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); + options.ListenAnyIP( + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); } else if (builderContext.HostingEnvironment.IsDevelopment()) { try { - options.ListenAnyIP(appHost.HttpsPort, listenOptions => - { - listenOptions.UseHttps(); - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); + options.ListenAnyIP(appHost.HttpsPort, listenOptions => listenOptions.UseHttps()); } catch (InvalidOperationException ex) { From d34be6faf4cf2d8d916593e28cc7d0da5ec3a40a Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 1 Nov 2020 00:33:38 +0800 Subject: [PATCH 130/203] fix aac directstreaming --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 5 +++-- MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 996b1b5c1e..33256e4bf2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2675,9 +2675,10 @@ namespace MediaBrowser.Controller.MediaEncoding state.MediaSource = mediaSource; var request = state.BaseRequest; - if (!string.IsNullOrWhiteSpace(request.AudioCodec)) + var supportedAudioCodecs = state.SupportedAudioCodecs; + if (request != null && supportedAudioCodecs != null && supportedAudioCodecs.Length > 0) { - var supportedAudioCodecsList = request.AudioCodec.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + var supportedAudioCodecsList = supportedAudioCodecs.ToList(); ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 6cd0c70d2f..6e9362cd14 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -287,6 +287,11 @@ namespace MediaBrowser.Controller.MediaEncoding return BaseRequest.AudioChannels; } + if (BaseRequest.TranscodingMaxAudioChannels.HasValue) + { + return BaseRequest.TranscodingMaxAudioChannels; + } + if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "audiochannels"); From 0bcf4c02b4281ed897b2ea9415db0397a9875ec0 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 31 Oct 2020 15:50:56 -0400 Subject: [PATCH 131/203] Remove jellyfin-ffmpeg dep from server package Being added to the metapackage instead. --- debian/control | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/control b/debian/control index 39c2aa055b..9216d24fe3 100644 --- a/debian/control +++ b/debian/control @@ -20,7 +20,6 @@ Breaks: jellyfin (<<10.6.0) Architecture: any Depends: at, libsqlite3-0, - jellyfin-ffmpeg (>= 4.2.1-2), libfontconfig1, libfreetype6, libssl1.1 From e5237384d5f4d525f0427b4571d2c4a2182adae8 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 1 Nov 2020 04:20:25 +0800 Subject: [PATCH 132/203] fix the route of audio containers --- Jellyfin.Api/Controllers/UniversalAudioController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index df20a92b3d..70848aed6c 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -88,16 +88,14 @@ namespace Jellyfin.Api.Controllers /// Redirected to remote audio stream. /// A containing the audio file. [HttpGet("Audio/{itemId}/universal")] - [HttpGet("Audio/{itemId}/universal.{container}", Name = "GetUniversalAudioStream_2")] [HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")] - [HttpHead("Audio/{itemId}/universal.{container}", Name = "HeadUniversalAudioStream_2")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status302Found)] [ProducesAudioFile] public async Task GetUniversalAudioStream( [FromRoute, Required] Guid itemId, - [FromRoute] string? container, + [FromQuery] string? container, [FromQuery] string? mediaSourceId, [FromQuery] string? deviceId, [FromQuery] Guid? userId, From 599e20ab9b760e6cd8300e8d0e401e1856518db5 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 1 Nov 2020 18:45:42 +0800 Subject: [PATCH 133/203] fix music directplay on Gelli --- Jellyfin.Api/Controllers/UniversalAudioController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 70848aed6c..a219a74cf8 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -274,7 +274,7 @@ namespace Jellyfin.Api.Controllers foreach (var cont in containers) { - var parts = RequestHelpers.Split(cont, ',', true); + var parts = RequestHelpers.Split(cont, '|', true); var audioCodecs = parts.Length == 1 ? null : string.Join(",", parts.Skip(1).ToArray()); From e41e832495765ec057ed5cc86eb8b3019e3f71c8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 1 Nov 2020 10:52:32 -0700 Subject: [PATCH 134/203] Dependency catch up --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 6 +++--- .../Jellyfin.Server.Implementations.Tests.csproj | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 07f08ec72b..a64d2e1cdb 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -43,7 +43,7 @@ - + diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 24400eae53..9465fe42c2 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -19,7 +19,7 @@ - + diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index e7993d2e7c..0236f2ac1e 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -13,9 +13,9 @@ - - - + + + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 6e5a908022..05323490e2 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -14,8 +14,8 @@ - - + + From ceecc80bb3816ca41d470e3b537a1bf7b632e8e2 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 1 Nov 2020 18:32:41 -0700 Subject: [PATCH 135/203] Allow configuration of ActivityLogRetention --- .../ScheduledTasks/Tasks/CleanActivityLogTask.cs | 16 +++++++++++++--- .../Configuration/ServerConfiguration.cs | 6 ++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 50bc091c83..4abbf784b2 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; @@ -16,18 +17,22 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks { private readonly ILocalizationManager _localization; private readonly IActivityManager _activityManager; + private readonly IServerConfigurationManager _serverConfigurationManager; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public CleanActivityLogTask( ILocalizationManager localization, - IActivityManager activityManager) + IActivityManager activityManager, + IServerConfigurationManager serverConfigurationManager) { _localization = localization; _activityManager = activityManager; + _serverConfigurationManager = serverConfigurationManager; } /// @@ -54,8 +59,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// public Task Execute(CancellationToken cancellationToken, IProgress progress) { - // TODO allow configure - var startDate = DateTime.UtcNow.AddDays(-30); + var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays; + if (!retentionDays.HasValue || retentionDays <= 0) + { + throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}"); + } + + var startDate = DateTime.UtcNow.AddDays(retentionDays.Value * -1); return _activityManager.CleanAsync(startDate); } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 8b78ad842e..23a5201f7a 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -271,6 +271,11 @@ namespace MediaBrowser.Model.Configuration /// public string[] KnownProxies { get; set; } + /// + /// Gets or sets the number of days we should retain activity logs. + /// + public int? ActivityLogRetentionDays { get; set; } + /// /// Initializes a new instance of the class. /// @@ -381,6 +386,7 @@ namespace MediaBrowser.Model.Configuration SlowResponseThresholdMs = 500; CorsHosts = new[] { "*" }; KnownProxies = Array.Empty(); + ActivityLogRetentionDays = 30; } } From 826c24142e43550bd8de114ae19e3789677cfdc3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 1 Nov 2020 18:53:02 -0700 Subject: [PATCH 136/203] Fix Genres separator --- Jellyfin.Api/Controllers/ArtistsController.cs | 4 ++-- Jellyfin.Api/Controllers/LiveTvController.cs | 4 ++-- Jellyfin.Api/Controllers/StudiosController.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 610c2aa646..24843c1844 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -148,7 +148,7 @@ namespace Jellyfin.Api.Controllers NameStartsWithOrGreater = nameStartsWithOrGreater, Tags = RequestHelpers.Split(tags, ',', true), OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), - Genres = RequestHelpers.Split(genres, ',', true), + Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), StudioIds = RequestHelpers.GetGuids(studioIds), Person = person, @@ -357,7 +357,7 @@ namespace Jellyfin.Api.Controllers NameStartsWithOrGreater = nameStartsWithOrGreater, Tags = RequestHelpers.Split(tags, ',', true), OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), - Genres = RequestHelpers.Split(genres, ',', true), + Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), StudioIds = RequestHelpers.GetGuids(studioIds), Person = person, diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 22f140ea62..f89c31e8b2 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -592,7 +592,7 @@ namespace Jellyfin.Api.Controllers IsKids = isKids, IsSports = isSports, SeriesTimerId = seriesTimerId, - Genres = RequestHelpers.Split(genres, ',', true), + Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds) }; @@ -648,7 +648,7 @@ namespace Jellyfin.Api.Controllers IsKids = body.IsKids, IsSports = body.IsSports, SeriesTimerId = body.SeriesTimerId, - Genres = RequestHelpers.Split(body.Genres, ',', true), + Genres = RequestHelpers.Split(body.Genres, '|', true), GenreIds = RequestHelpers.GetGuids(body.GenreIds) }; diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index 1d2bf22551..831a9ab8fb 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -147,7 +147,7 @@ namespace Jellyfin.Api.Controllers NameStartsWithOrGreater = nameStartsWithOrGreater, Tags = RequestHelpers.Split(tags, ',', true), OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), - Genres = RequestHelpers.Split(genres, ',', true), + Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), StudioIds = RequestHelpers.GetGuids(studioIds), Person = person, From c1ec46e92b2f5184ea0c7c1f84c19fc154fb15ad Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 1 Nov 2020 18:53:27 -0700 Subject: [PATCH 137/203] Fix Tags separator --- Jellyfin.Api/Controllers/ArtistsController.cs | 4 ++-- Jellyfin.Api/Controllers/StudiosController.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 24843c1844..24ccdecb72 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -146,7 +146,7 @@ namespace Jellyfin.Api.Controllers NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, - Tags = RequestHelpers.Split(tags, ',', true), + Tags = RequestHelpers.Split(tags, '|', true), OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), @@ -355,7 +355,7 @@ namespace Jellyfin.Api.Controllers NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, - Tags = RequestHelpers.Split(tags, ',', true), + Tags = RequestHelpers.Split(tags, '|', true), OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index 831a9ab8fb..45398dfd3c 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -145,7 +145,7 @@ namespace Jellyfin.Api.Controllers NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, - Tags = RequestHelpers.Split(tags, ',', true), + Tags = RequestHelpers.Split(tags, '|', true), OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), From dd3507bbbfd656a4138ac9428bebaa67e11cb076 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 1 Nov 2020 18:54:00 -0700 Subject: [PATCH 138/203] Fix OfficialRatings separator --- Jellyfin.Api/Controllers/ArtistsController.cs | 4 ++-- Jellyfin.Api/Controllers/StudiosController.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 24ccdecb72..59d3ae2e11 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -147,7 +147,7 @@ namespace Jellyfin.Api.Controllers NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, Tags = RequestHelpers.Split(tags, '|', true), - OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), + OfficialRatings = RequestHelpers.Split(officialRatings, '|', true), Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), StudioIds = RequestHelpers.GetGuids(studioIds), @@ -356,7 +356,7 @@ namespace Jellyfin.Api.Controllers NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, Tags = RequestHelpers.Split(tags, '|', true), - OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), + OfficialRatings = RequestHelpers.Split(officialRatings, '|', true), Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), StudioIds = RequestHelpers.GetGuids(studioIds), diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index 45398dfd3c..b3c345dd4b 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -146,7 +146,7 @@ namespace Jellyfin.Api.Controllers NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, Tags = RequestHelpers.Split(tags, '|', true), - OfficialRatings = RequestHelpers.Split(officialRatings, ',', true), + OfficialRatings = RequestHelpers.Split(officialRatings, '|', true), Genres = RequestHelpers.Split(genres, '|', true), GenreIds = RequestHelpers.GetGuids(genreIds), StudioIds = RequestHelpers.GetGuids(studioIds), From 6763d456fff57a08ebde3e1b68169a585c843d69 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 1 Nov 2020 19:23:28 -0700 Subject: [PATCH 139/203] Set UserAgent when getting M3u playlist --- .../LiveTv/TunerHosts/M3UTunerHost.cs | 6 +++-- .../LiveTv/TunerHosts/M3uParser.cs | 26 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 8107bc427b..4b170b2e4e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -65,7 +65,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var channelIdPrefix = GetFullChannelIdPrefix(info); - return await new M3uParser(Logger, _httpClientFactory, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false); + return await new M3uParser(Logger, _httpClientFactory, _appHost) + .Parse(info, channelIdPrefix, cancellationToken) + .ConfigureAwait(false); } public Task> GetTunerInfos(CancellationToken cancellationToken) @@ -126,7 +128,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task Validate(TunerHostInfo info) { - using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false)) + using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false)) { } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index f066a749e3..c064e2fe6c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -13,6 +13,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.LiveTv; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.TunerHosts @@ -30,12 +31,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts _appHost = appHost; } - public async Task> Parse(string url, string channelIdPrefix, string tunerHostId, CancellationToken cancellationToken) + public async Task> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken) { // Read the file and display it line by line. - using (var reader = new StreamReader(await GetListingsStream(url, cancellationToken).ConfigureAwait(false))) + using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false))) { - return GetChannels(reader, channelIdPrefix, tunerHostId); + return GetChannels(reader, channelIdPrefix, info.Id); } } @@ -48,15 +49,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - public Task GetListingsStream(string url, CancellationToken cancellationToken) + public async Task GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken) { - if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { - return _httpClientFactory.CreateClient(NamedClient.Default) - .GetStreamAsync(url); + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url); + if (!string.IsNullOrEmpty(info.UserAgent)) + { + requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent); + } + + var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(requestMessage, cancellationToken) + .ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); } - return Task.FromResult((Stream)File.OpenRead(url)); + return File.OpenRead(info.Url); } private const string ExtInfPrefix = "#EXTINF:"; From 6a07e93ddd934420620297d510d4c4b9122463cb Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 1 Nov 2020 20:37:03 -0700 Subject: [PATCH 140/203] Fix endpoint auth --- Jellyfin.Api/Controllers/DevicesController.cs | 6 +----- Jellyfin.Api/Controllers/SuggestionsController.cs | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 74380c2eff..b3e3490c2a 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Api.Controllers /// /// Devices Controller. /// - [Authorize(Policy = Policies.DefaultAuthorization)] + [Authorize(Policy = Policies.RequiresElevation)] public class DevicesController : BaseJellyfinApiController { private readonly IDeviceManager _deviceManager; @@ -46,7 +46,6 @@ namespace Jellyfin.Api.Controllers /// Devices retrieved. /// An containing the list of devices. [HttpGet] - [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { @@ -62,7 +61,6 @@ namespace Jellyfin.Api.Controllers /// Device not found. /// An containing the device info on success, or a if the device could not be found. [HttpGet("Info")] - [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetDeviceInfo([FromQuery, Required] string id) @@ -84,7 +82,6 @@ namespace Jellyfin.Api.Controllers /// Device not found. /// An containing the device info on success, or a if the device could not be found. [HttpGet("Options")] - [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetDeviceOptions([FromQuery, Required] string id) @@ -107,7 +104,6 @@ namespace Jellyfin.Api.Controllers /// Device not found. /// A on success, or a if the device could not be found. [HttpPost("Options")] - [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateDeviceOptions( diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index d7c81a3ab6..ad64adfbad 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Linq; +using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Data.Enums; @@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -18,6 +20,7 @@ namespace Jellyfin.Api.Controllers /// The suggestions controller. /// [Route("")] + [Authorize(Policy = Policies.DefaultAuthorization)] public class SuggestionsController : BaseJellyfinApiController { private readonly IDtoService _dtoService; From 19d77c99ae77339e919090bfea1244e1eba26b0c Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 2 Nov 2020 09:23:29 +0100 Subject: [PATCH 141/203] Save new display preferences --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 3 +++ MediaBrowser.Controller/IDisplayPreferencesManager.cs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 874467c75b..76f5717e30 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -81,6 +81,9 @@ namespace Jellyfin.Api.Controllers dto.CustomPrefs["enableNextVideoInfoOverlay"] = displayPreferences.EnableNextVideoInfoOverlay.ToString(CultureInfo.InvariantCulture); dto.CustomPrefs["tvhome"] = displayPreferences.TvHome; + // This will essentially be a noop if no changes have been made, but new prefs must be saved at least. + _displayPreferencesManager.SaveChanges(); + return dto; } diff --git a/MediaBrowser.Controller/IDisplayPreferencesManager.cs b/MediaBrowser.Controller/IDisplayPreferencesManager.cs index b35f830960..6658269bdb 100644 --- a/MediaBrowser.Controller/IDisplayPreferencesManager.cs +++ b/MediaBrowser.Controller/IDisplayPreferencesManager.cs @@ -12,6 +12,9 @@ namespace MediaBrowser.Controller /// /// Gets the display preferences for the user and client. /// + /// + /// This will create the display preferences if it does not exist, but it will not save automatically. + /// /// The user's id. /// The client string. /// The associated display preferences. @@ -20,6 +23,9 @@ namespace MediaBrowser.Controller /// /// Gets the default item display preferences for the user and client. /// + /// + /// This will create the item display preferences if it does not exist, but it will not save automatically. + /// /// The user id. /// The item id. /// The client string. From de36c8433e6b64e15ffb8535c0e6d1ccb264c49e Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Mon, 2 Nov 2020 03:52:14 -0700 Subject: [PATCH 142/203] Update Emby.Server.Implementations/Localization/Core/en-US.json Co-authored-by: Claus Vium --- Emby.Server.Implementations/Localization/Core/en-US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index bc973c9737..6d8b222b44 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -96,7 +96,7 @@ "TasksApplicationCategory": "Application", "TasksChannelsCategory": "Internet Channels", "TaskCleanActivityLog": "Clean Activity Log", - "TaskCleanActivityLogDescription": "Deletes activity log entries older then the configured age.", + "TaskCleanActivityLogDescription": "Deletes activity log entries older than the configured age.", "TaskCleanCache": "Clean Cache Directory", "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", "TaskRefreshChapterImages": "Extract Chapter Images", From af56d5c2dc2a2aa2cce0513b906502a59056dfb2 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 2 Nov 2020 11:53:23 +0100 Subject: [PATCH 143/203] Rename itemIds to ids --- Jellyfin.Api/Controllers/CollectionController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index 2fc697a6aa..eae06b767c 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -83,14 +83,14 @@ namespace Jellyfin.Api.Controllers /// Adds items to a collection. /// /// The collection id. - /// Item ids, comma delimited. + /// Item ids, comma delimited. /// Items added to collection. /// A indicating success. [HttpPost("{collectionId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public async Task AddToCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string itemIds) + public async Task AddToCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string ids) { - await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true); + await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(ids)).ConfigureAwait(true); return NoContent(); } @@ -98,14 +98,14 @@ namespace Jellyfin.Api.Controllers /// Removes items from a collection. /// /// The collection id. - /// Item ids, comma delimited. + /// Item ids, comma delimited. /// Items removed from collection. /// A indicating success. [HttpDelete("{collectionId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public async Task RemoveFromCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string itemIds) + public async Task RemoveFromCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string ids) { - await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false); + await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(ids)).ConfigureAwait(false); return NoContent(); } } From 25f93a9af4ee3caa3bbb42996e5c65f0c3adb1de Mon Sep 17 00:00:00 2001 From: dkanada Date: Tue, 3 Nov 2020 23:18:30 +0900 Subject: [PATCH 144/203] disable compatibility checks until they work again --- .ci/azure-pipelines-abi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml index 4d38a906e6..52abdd0ea5 100644 --- a/.ci/azure-pipelines-abi.yml +++ b/.ci/azure-pipelines-abi.yml @@ -83,6 +83,7 @@ jobs: - task: DotNetCoreCLI@2 displayName: 'Execute ABI Compatibility Check Tool' + enabled: false inputs: command: custom custom: compat From b4d52d8009d8e4a6836dc431ac5f336910a07d6c Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 3 Nov 2020 16:38:47 -0700 Subject: [PATCH 145/203] Apply patch --- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 5 +++++ .../LiveTv/TunerHosts/SharedHttpStream.cs | 5 +++++ Jellyfin.Api/Controllers/LiveTvController.cs | 7 ++----- Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 12 ++++++++---- .../Library/IMediaSourceManager.cs | 2 ++ 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 6730751d50..858c10030d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -131,6 +131,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun await taskCompletionSource.Task.ConfigureAwait(false); } + public string GetFilePath() + { + return TempFilePath; + } + private Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { return Task.Run(async () => diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 10e5eab73c..2e1b895096 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -122,6 +122,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } + public string GetFilePath() + { + return TempFilePath; + } + private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { return Task.Run(async () => diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 58c7473c23..88a7542cee 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1220,11 +1220,8 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - await new ProgressiveFileCopier(liveStreamInfo, null, _transcodingJobHelper, CancellationToken.None) - .WriteToAsync(Response.Body, CancellationToken.None) - .ConfigureAwait(false); - Response.ContentType = MimeTypes.GetMimeType("file." + container); - return Ok(); + var liveStream = new ProgressiveFileStream(liveStreamInfo.GetFilePath(), null, _transcodingJobHelper); + return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container)); } private void AssertUserCanManageLiveTv() diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index b3566b6f80..824870c7ef 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -82,20 +82,23 @@ namespace Jellyfin.Api.Helpers int totalBytesRead = 0; int remainingBytesToRead = count; + int newOffset = offset; while (remainingBytesToRead > 0) { cancellationToken.ThrowIfCancellationRequested(); int bytesRead; if (_allowAsyncFileRead) { - bytesRead = await _fileStream.ReadAsync(buffer, offset, remainingBytesToRead, cancellationToken).ConfigureAwait(false); + bytesRead = await _fileStream.ReadAsync(buffer, newOffset, remainingBytesToRead, cancellationToken).ConfigureAwait(false); } else { - bytesRead = _fileStream.Read(buffer, offset, remainingBytesToRead); + bytesRead = _fileStream.Read(buffer, newOffset, remainingBytesToRead); } remainingBytesToRead -= bytesRead; + newOffset += bytesRead; + if (bytesRead > 0) { _bytesWritten += bytesRead; @@ -108,12 +111,13 @@ namespace Jellyfin.Api.Helpers } else { - if (_job == null || _job.HasExited) + // If the job is null it's a live stream and will require user action to close + if (_job?.HasExited ?? false) { break; } - await Task.Delay(100, cancellationToken).ConfigureAwait(false); + await Task.Delay(50, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 22bf9488f7..21c6ef2af1 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -115,5 +115,7 @@ namespace MediaBrowser.Controller.Library public interface IDirectStreamProvider { Task CopyToAsync(Stream stream, CancellationToken cancellationToken); + + string GetFilePath(); } } From d9ea1ac12d03b2d299c162f9d9d33c840eeb9a78 Mon Sep 17 00:00:00 2001 From: dkanada Date: Wed, 4 Nov 2020 13:49:49 +0900 Subject: [PATCH 146/203] disable two more tasks to fix the failures --- .ci/azure-pipelines-abi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml index 52abdd0ea5..b558d2a6f0 100644 --- a/.ci/azure-pipelines-abi.yml +++ b/.ci/azure-pipelines-abi.yml @@ -62,6 +62,7 @@ jobs: - task: DownloadPipelineArtifact@2 displayName: 'Download Reference Assembly Build Artifact' + enabled: false inputs: source: "specific" artifact: "$(NugetPackageName)" @@ -73,6 +74,7 @@ jobs: - task: CopyFiles@2 displayName: 'Copy Reference Assembly Build Artifact' + enabled: false inputs: sourceFolder: $(System.ArtifactsDirectory)/current-artifacts contents: '**/*.dll' From 584b4fa41f4a19a7df2a78b408e3763ca0ff4027 Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 5 Nov 2020 12:27:22 +0100 Subject: [PATCH 147/203] Fix Persons, Genres and Studios endpoints --- .../Data/SqliteItemRepository.cs | 63 +++--- .../Library/LibraryManager.cs | 15 ++ Jellyfin.Api/Controllers/GenresController.cs | 141 ++----------- Jellyfin.Api/Controllers/PersonsController.cs | 185 +++--------------- Jellyfin.Api/Controllers/StudiosController.cs | 134 +------------ Jellyfin.Api/Helpers/RequestHelpers.cs | 41 +++- .../Entities/InternalPeopleQuery.cs | 5 + .../Library/ILibraryManager.cs | 2 + 8 files changed, 133 insertions(+), 453 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 81e8e38b33..acb75e9b89 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -5002,26 +5002,33 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); - var commandText = "select Distinct Name from People"; + var commandText = new StringBuilder("select Distinct p.Name from People p"); + + if (query.User != null && query.IsFavorite.HasValue) + { + commandText.Append(" LEFT JOIN TypedBaseItems tbi ON tbi.Name=p.Name AND tbi.Type='"); + commandText.Append(typeof(Person).FullName); + commandText.Append("' LEFT JOIN UserDatas ON tbi.UserDataKey=key AND userId=@UserId"); + } var whereClauses = GetPeopleWhereClauses(query, null); if (whereClauses.Count != 0) { - commandText += " where " + string.Join(" AND ", whereClauses); + commandText.Append(" where ").Append(string.Join(" AND ", whereClauses)); } - commandText += " order by ListOrder"; + commandText.Append(" order by ListOrder"); if (query.Limit > 0) { - commandText += " LIMIT " + query.Limit; + commandText.Append(" LIMIT ").Append(query.Limit); } using (var connection = GetConnection(true)) { var list = new List(); - using (var statement = PrepareStatement(connection, commandText)) + using (var statement = PrepareStatement(connection, commandText.ToString())) { // Run this again to bind the params GetPeopleWhereClauses(query, statement); @@ -5087,19 +5094,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (!query.ItemId.Equals(Guid.Empty)) { whereClauses.Add("ItemId=@ItemId"); - if (statement != null) - { - statement.TryBind("@ItemId", query.ItemId.ToByteArray()); - } + statement?.TryBind("@ItemId", query.ItemId.ToByteArray()); } if (!query.AppearsInItemId.Equals(Guid.Empty)) { - whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)"); - if (statement != null) - { - statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray()); - } + whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)"); + statement?.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray()); } var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); @@ -5107,10 +5108,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (queryPersonTypes.Count == 1) { whereClauses.Add("PersonType=@PersonType"); - if (statement != null) - { - statement.TryBind("@PersonType", queryPersonTypes[0]); - } + statement?.TryBind("@PersonType", queryPersonTypes[0]); } else if (queryPersonTypes.Count > 1) { @@ -5124,10 +5122,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (queryExcludePersonTypes.Count == 1) { whereClauses.Add("PersonType<>@PersonType"); - if (statement != null) - { - statement.TryBind("@PersonType", queryExcludePersonTypes[0]); - } + statement?.TryBind("@PersonType", queryExcludePersonTypes[0]); } else if (queryExcludePersonTypes.Count > 1) { @@ -5139,19 +5134,24 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (query.MaxListOrder.HasValue) { whereClauses.Add("ListOrder<=@MaxListOrder"); - if (statement != null) - { - statement.TryBind("@MaxListOrder", query.MaxListOrder.Value); - } + statement?.TryBind("@MaxListOrder", query.MaxListOrder.Value); } if (!string.IsNullOrWhiteSpace(query.NameContains)) { - whereClauses.Add("Name like @NameContains"); - if (statement != null) - { - statement.TryBind("@NameContains", "%" + query.NameContains + "%"); - } + whereClauses.Add("p.Name like @NameContains"); + statement?.TryBind("@NameContains", "%" + query.NameContains + "%"); + } + + if (query.IsFavorite.HasValue) + { + whereClauses.Add("isFavorite=@IsFavorite"); + statement?.TryBind("@IsFavorite", query.IsFavorite.Value); + } + + if (query.User != null) + { + statement?.TryBind("@UserId", query.User.InternalId); } return whereClauses; @@ -5420,6 +5420,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type NameStartsWithOrGreater = query.NameStartsWithOrGreater, Tags = query.Tags, OfficialRatings = query.OfficialRatings, + StudioIds = query.StudioIds, GenreIds = query.GenreIds, Genres = query.Genres, Years = query.Years, diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 00282b71a5..f16eda1ec2 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2440,6 +2440,21 @@ namespace Emby.Server.Implementations.Library new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); } + public BaseItem GetParentItem(string parentId, Guid? userId) + { + if (!string.IsNullOrEmpty(parentId)) + { + return GetItemById(new Guid(parentId)); + } + + if (userId.HasValue && userId != Guid.Empty) + { + return GetUserRootFolder(); + } + + return RootFolder; + } + /// public bool IsVideoFile(string path) { diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index aa7d02de00..a174d9239b 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -1,11 +1,9 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; -using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -49,7 +47,6 @@ namespace Jellyfin.Api.Controllers /// /// Gets all genres from a given item, folder, or the entire library. /// - /// Optional filter by minimum community rating. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// The search term. @@ -57,22 +54,9 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. - /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. - /// Optional filter by MediaType. Allows multiple, comma delimited. - /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited. - /// Optional, include user data. /// Optional, the max number of images to return, per image type. /// Optional. The image types to include in the output. - /// Optional. If specified, results will be filtered to include only those containing the specified person. - /// Optional. If specified, results will be filtered to include only those containing the specified person id. - /// Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited. - /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited. /// User id. /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. @@ -84,7 +68,6 @@ namespace Jellyfin.Api.Controllers [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetGenres( - [FromQuery] double? minCommunityRating, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? searchTerm, @@ -92,22 +75,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery] string? mediaTypes, - [FromQuery] string? genres, - [FromQuery] string? genreIds, - [FromQuery] string? officialRatings, - [FromQuery] string? tags, - [FromQuery] string? years, - [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery] ImageType[] enableImageTypes, - [FromQuery] string? person, - [FromQuery] string? personIds, - [FromQuery] string? personTypes, - [FromQuery] string? studios, - [FromQuery] string? studioIds, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, @@ -118,42 +88,22 @@ namespace Jellyfin.Api.Controllers var dtoOptions = new DtoOptions() .AddItemFields(fields) .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); - User? user = null; - BaseItem parentItem; + User? user = userId.HasValue && userId != Guid.Empty ? _userManager.GetUserById(userId.Value) : null; - if (userId.HasValue && !userId.Equals(Guid.Empty)) - { - user = _userManager.GetUserById(userId.Value); - parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId); - } - else - { - parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId); - } + var parentItem = _libraryManager.GetParentItem(parentId, userId); var query = new InternalItemsQuery(user) { ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true), IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true), - MediaTypes = RequestHelpers.Split(mediaTypes, ',', true), StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, - Tags = RequestHelpers.Split(tags, '|', true), - OfficialRatings = RequestHelpers.Split(officialRatings, '|', true), - Genres = RequestHelpers.Split(genres, '|', true), - GenreIds = RequestHelpers.GetGuids(genreIds), - StudioIds = RequestHelpers.GetGuids(studioIds), - Person = person, - PersonIds = RequestHelpers.GetGuids(personIds), - PersonTypes = RequestHelpers.Split(personTypes, ',', true), - Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(), - MinCommunityRating = minCommunityRating, DtoOptions = dtoOptions, SearchTerm = searchTerm, EnableTotalRecordCount = enableTotalRecordCount @@ -171,87 +121,20 @@ namespace Jellyfin.Api.Controllers } } - // Studios - if (!string.IsNullOrEmpty(studios)) + QueryResult<(BaseItem, ItemCounts)> result; + if (parentItem is ICollectionFolder parentCollectionFolder + && (string.Equals(parentCollectionFolder.CollectionType, CollectionType.Music) + || string.Equals(parentCollectionFolder.CollectionType, CollectionType.MusicVideos))) { - query.StudioIds = studios.Split('|') - .Select(i => - { - try - { - return _libraryManager.GetStudio(i); - } - catch - { - return null; - } - }).Where(i => i != null) - .Select(i => i!.Id) - .ToArray(); + result = _libraryManager.GetMusicGenres(query); } - - foreach (var filter in filters) + else { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } + result = _libraryManager.GetGenres(query); } - var result = new QueryResult<(BaseItem, ItemCounts)>(); - - var dtos = result.Items.Select(i => - { - var (baseItem, counts) = i; - var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); - - if (!string.IsNullOrWhiteSpace(includeItemTypes)) - { - dto.ChildCount = counts.ItemCount; - dto.ProgramCount = counts.ProgramCount; - dto.SeriesCount = counts.SeriesCount; - dto.EpisodeCount = counts.EpisodeCount; - dto.MovieCount = counts.MovieCount; - dto.TrailerCount = counts.TrailerCount; - dto.AlbumCount = counts.AlbumCount; - dto.SongCount = counts.SongCount; - dto.ArtistCount = counts.ArtistCount; - } - - return dto; - }); - - return new QueryResult - { - Items = dtos.ToArray(), - TotalRecordCount = result.TotalRecordCount - }; + var shouldIncludeItemTypes = !string.IsNullOrEmpty(includeItemTypes); + return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); } /// diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index f173f75ba3..1e0bdb6bc7 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; @@ -28,6 +27,7 @@ namespace Jellyfin.Api.Controllers private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; private readonly IUserManager _userManager; + private readonly IUserDataManager _userDataManager; /// /// Initializes a new instance of the class. @@ -35,84 +35,53 @@ namespace Jellyfin.Api.Controllers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public PersonsController( ILibraryManager libraryManager, IDtoService dtoService, - IUserManager userManager) + IUserManager userManager, + IUserDataManager userDataManager) { _libraryManager = libraryManager; _dtoService = dtoService; _userManager = userManager; + _userDataManager = userDataManager; } /// - /// Gets all persons from a given item, folder, or the entire library. + /// Gets all persons. /// - /// Optional filter by minimum community rating. - /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// The search term. - /// Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. - /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. - /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. - /// Optional filter by items that are marked as favorite, or not. - /// Optional filter by MediaType. Allows multiple, comma delimited. - /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited. + /// Optional filter by items that are marked as favorite, or not. userId is required. /// Optional, include user data. /// Optional, the max number of images to return, per image type. /// Optional. The image types to include in the output. - /// Optional. If specified, results will be filtered to include only those containing the specified person. - /// Optional. If specified, results will be filtered to include only those containing the specified person id. - /// Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited. - /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited. + /// Optional. If specified results will be filtered to exclude those containing the specified PersonType. Allows multiple, comma-delimited. + /// Optional. If specified results will be filtered to include only those containing the specified PersonType. Allows multiple, comma-delimited. + /// Optional. If specified, person results will be filtered on items related to said persons. /// User id. - /// Optional filter by items whose name is sorted equally or greater than a given input string. - /// Optional filter by items whose name is sorted equally than a given input string. - /// Optional filter by items whose name is equally or lesser than a given input string. /// Optional, include image information in output. - /// Optional. Include total record count. /// Persons returned. /// An containing the queryresult of persons. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetPersons( - [FromQuery] double? minCommunityRating, - [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? searchTerm, - [FromQuery] string? parentId, [FromQuery] string? fields, - [FromQuery] string? excludeItemTypes, - [FromQuery] string? includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery] string? mediaTypes, - [FromQuery] string? genres, - [FromQuery] string? genreIds, - [FromQuery] string? officialRatings, - [FromQuery] string? tags, - [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery] ImageType[] enableImageTypes, - [FromQuery] string? person, - [FromQuery] string? personIds, + [FromQuery] string? excludePersonTypes, [FromQuery] string? personTypes, - [FromQuery] string? studios, - [FromQuery] string? studioIds, + [FromQuery] string? appearsInItemId, [FromQuery] Guid? userId, - [FromQuery] string? nameStartsWithOrGreater, - [FromQuery] string? nameStartsWith, - [FromQuery] string? nameLessThan, - [FromQuery] bool? enableImages = true, - [FromQuery] bool enableTotalRecordCount = true) + [FromQuery] bool? enableImages = true) { var dtoOptions = new DtoOptions() .AddItemFields(fields) @@ -120,136 +89,28 @@ namespace Jellyfin.Api.Controllers .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); User? user = null; - BaseItem parentItem; if (userId.HasValue && !userId.Equals(Guid.Empty)) { user = _userManager.GetUserById(userId.Value); - parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId); - } - else - { - parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId); } - var query = new InternalItemsQuery(user) + var isFavoriteInFilters = filters.Any(f => f == ItemFilter.IsFavorite); + var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery { - ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true), - IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true), - MediaTypes = RequestHelpers.Split(mediaTypes, ',', true), - StartIndex = startIndex, - Limit = limit, - IsFavorite = isFavorite, - NameLessThan = nameLessThan, - NameStartsWith = nameStartsWith, - NameStartsWithOrGreater = nameStartsWithOrGreater, - Tags = RequestHelpers.Split(tags, '|', true), - OfficialRatings = RequestHelpers.Split(officialRatings, '|', true), - Genres = RequestHelpers.Split(genres, '|', true), - GenreIds = RequestHelpers.GetGuids(genreIds), - StudioIds = RequestHelpers.GetGuids(studioIds), - Person = person, - PersonIds = RequestHelpers.GetGuids(personIds), PersonTypes = RequestHelpers.Split(personTypes, ',', true), - Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(), - MinCommunityRating = minCommunityRating, - DtoOptions = dtoOptions, - SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount - }; - - if (!string.IsNullOrWhiteSpace(parentId)) - { - if (parentItem is Folder) - { - query.AncestorIds = new[] { new Guid(parentId) }; - } - else - { - query.ItemIds = new[] { new Guid(parentId) }; - } - } - - // Studios - if (!string.IsNullOrEmpty(studios)) - { - query.StudioIds = studios.Split('|') - .Select(i => - { - try - { - return _libraryManager.GetStudio(i); - } - catch - { - return null; - } - }).Where(i => i != null) - .Select(i => i!.Id) - .ToArray(); - } - - foreach (var filter in filters) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - var result = new QueryResult<(BaseItem, ItemCounts)>(); - - var dtos = result.Items.Select(i => - { - var (baseItem, counts) = i; - var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); - - if (!string.IsNullOrWhiteSpace(includeItemTypes)) - { - dto.ChildCount = counts.ItemCount; - dto.ProgramCount = counts.ProgramCount; - dto.SeriesCount = counts.SeriesCount; - dto.EpisodeCount = counts.EpisodeCount; - dto.MovieCount = counts.MovieCount; - dto.TrailerCount = counts.TrailerCount; - dto.AlbumCount = counts.AlbumCount; - dto.SongCount = counts.SongCount; - dto.ArtistCount = counts.ArtistCount; - } - - return dto; + ExcludePersonTypes = RequestHelpers.Split(excludePersonTypes, ',', true), + NameContains = searchTerm, + User = user, + IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite, + AppearsInItemId = string.IsNullOrEmpty(appearsInItemId) ? Guid.Empty : Guid.Parse(appearsInItemId), + Limit = limit ?? 0 }); return new QueryResult { - Items = dtos.ToArray(), - TotalRecordCount = result.TotalRecordCount + Items = peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray(), + TotalRecordCount = peopleItems.Count }; } diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index 94eb3f7faa..c5fcfb3569 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -1,10 +1,8 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; -using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -47,7 +45,6 @@ namespace Jellyfin.Api.Controllers /// /// Gets all studios from a given item, folder, or the entire library. /// - /// Optional filter by minimum community rating. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Optional. Search term. @@ -55,22 +52,10 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. - /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. - /// Optional filter by MediaType. Allows multiple, comma delimited. - /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited. /// Optional, include user data. /// Optional, the max number of images to return, per image type. /// Optional. The image types to include in the output. - /// Optional. If specified, results will be filtered to include only those containing the specified person. - /// Optional. If specified, results will be filtered to include only those containing the specified person ids. - /// Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited. - /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited. /// User id. /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. @@ -82,7 +67,6 @@ namespace Jellyfin.Api.Controllers [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetStudios( - [FromQuery] double? minCommunityRating, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? searchTerm, @@ -90,22 +74,10 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery] string? mediaTypes, - [FromQuery] string? genres, - [FromQuery] string? genreIds, - [FromQuery] string? officialRatings, - [FromQuery] string? tags, - [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery] ImageType[] enableImageTypes, - [FromQuery] string? person, - [FromQuery] string? personIds, - [FromQuery] string? personTypes, - [FromQuery] string? studios, - [FromQuery] string? studioIds, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, @@ -118,44 +90,23 @@ namespace Jellyfin.Api.Controllers .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - User? user = null; - BaseItem parentItem; + User? user = userId.HasValue && userId != Guid.Empty ? _userManager.GetUserById(userId.Value) : null; - if (userId.HasValue && !userId.Equals(Guid.Empty)) - { - user = _userManager.GetUserById(userId.Value); - parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId); - } - else - { - parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId); - } + var parentItem = _libraryManager.GetParentItem(parentId, userId); var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true); var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true); - var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true); var query = new InternalItemsQuery(user) { ExcludeItemTypes = excludeItemTypesArr, IncludeItemTypes = includeItemTypesArr, - MediaTypes = mediaTypesArr, StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, - Tags = RequestHelpers.Split(tags, '|', true), - OfficialRatings = RequestHelpers.Split(officialRatings, '|', true), - Genres = RequestHelpers.Split(genres, '|', true), - GenreIds = RequestHelpers.GetGuids(genreIds), - StudioIds = RequestHelpers.GetGuids(studioIds), - Person = person, - PersonIds = RequestHelpers.GetGuids(personIds), - PersonTypes = RequestHelpers.Split(personTypes, ',', true), - Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(), - MinCommunityRating = minCommunityRating, DtoOptions = dtoOptions, SearchTerm = searchTerm, EnableTotalRecordCount = enableTotalRecordCount @@ -173,84 +124,9 @@ namespace Jellyfin.Api.Controllers } } - // Studios - if (!string.IsNullOrEmpty(studios)) - { - query.StudioIds = studios.Split('|').Select(i => - { - try - { - return _libraryManager.GetStudio(i); - } - catch - { - return null; - } - }).Where(i => i != null).Select(i => i!.Id) - .ToArray(); - } - - foreach (var filter in filters) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - var result = new QueryResult<(BaseItem, ItemCounts)>(); - var dtos = result.Items.Select(i => - { - var (baseItem, itemCounts) = i; - var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); - - if (!string.IsNullOrWhiteSpace(includeItemTypes)) - { - dto.ChildCount = itemCounts.ItemCount; - dto.ProgramCount = itemCounts.ProgramCount; - dto.SeriesCount = itemCounts.SeriesCount; - dto.EpisodeCount = itemCounts.EpisodeCount; - dto.MovieCount = itemCounts.MovieCount; - dto.TrailerCount = itemCounts.TrailerCount; - dto.AlbumCount = itemCounts.AlbumCount; - dto.SongCount = itemCounts.SongCount; - dto.ArtistCount = itemCounts.ArtistCount; - } - - return dto; - }); - - return new QueryResult - { - Items = dtos.ToArray(), - TotalRecordCount = result.TotalRecordCount - }; + var result = _libraryManager.GetStudios(query); + var shouldIncludeItemTypes = !string.IsNullOrEmpty(includeItemTypes); + return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); } /// diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 78d2b831c8..49632dd01a 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -1,11 +1,13 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Net; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Http; @@ -189,5 +191,40 @@ namespace Jellyfin.Api.Helpers .Select(i => i!.Value) .ToArray(); } + + internal static QueryResult CreateQueryResult( + QueryResult<(BaseItem, ItemCounts)> result, + DtoOptions dtoOptions, + IDtoService dtoService, + bool includeItemTypes, + User? user) + { + var dtos = result.Items.Select(i => + { + var (baseItem, counts) = i; + var dto = dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); + + if (includeItemTypes) + { + dto.ChildCount = counts.ItemCount; + dto.ProgramCount = counts.ProgramCount; + dto.SeriesCount = counts.SeriesCount; + dto.EpisodeCount = counts.EpisodeCount; + dto.MovieCount = counts.MovieCount; + dto.TrailerCount = counts.TrailerCount; + dto.AlbumCount = counts.AlbumCount; + dto.SongCount = counts.SongCount; + dto.ArtistCount = counts.ArtistCount; + } + + return dto; + }); + + return new QueryResult + { + Items = dtos.ToArray(), + TotalRecordCount = result.TotalRecordCount + }; + } } } diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs index 4e09ee5736..5b96a5af65 100644 --- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Entities; namespace MediaBrowser.Controller.Entities { @@ -23,6 +24,10 @@ namespace MediaBrowser.Controller.Entities public string NameContains { get; set; } + public User User { get; set; } + + public bool? IsFavorite { get; set; } + public InternalPeopleQuery() { PersonTypes = Array.Empty(); diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 32703c2fd1..c7c79df76d 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -570,5 +570,7 @@ namespace MediaBrowser.Controller.Library List streams, string videoPath, string[] files); + + BaseItem GetParentItem(string parentId, Guid? userId); } } From 6fdcd1205314653b4b7a454ac86134842e225356 Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 5 Nov 2020 12:53:23 +0100 Subject: [PATCH 148/203] Fix build and clean up MusicGenres --- Jellyfin.Api/Controllers/GenresController.cs | 4 +- .../Controllers/MusicGenresController.cs | 137 +----------------- 2 files changed, 7 insertions(+), 134 deletions(-) diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index a174d9239b..4e47658b06 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -123,8 +123,8 @@ namespace Jellyfin.Api.Controllers QueryResult<(BaseItem, ItemCounts)> result; if (parentItem is ICollectionFolder parentCollectionFolder - && (string.Equals(parentCollectionFolder.CollectionType, CollectionType.Music) - || string.Equals(parentCollectionFolder.CollectionType, CollectionType.MusicVideos))) + && (string.Equals(parentCollectionFolder.CollectionType, CollectionType.Music, StringComparison.Ordinal) + || string.Equals(parentCollectionFolder.CollectionType, CollectionType.MusicVideos, StringComparison.Ordinal))) { result = _libraryManager.GetMusicGenres(query); } diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 4de7ef8c88..24075b9052 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -1,11 +1,9 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; -using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -49,7 +47,6 @@ namespace Jellyfin.Api.Controllers /// /// Gets all music genres from a given item, folder, or the entire library. /// - /// Optional filter by minimum community rating. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// The search term. @@ -57,22 +54,9 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. - /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. - /// Optional filter by MediaType. Allows multiple, comma delimited. - /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited. - /// Optional, include user data. /// Optional, the max number of images to return, per image type. /// Optional. The image types to include in the output. - /// Optional. If specified, results will be filtered to include only those containing the specified person. - /// Optional. If specified, results will be filtered to include only those containing the specified person id. - /// Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited. - /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited. - /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited. /// User id. /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. @@ -83,7 +67,6 @@ namespace Jellyfin.Api.Controllers /// An containing the queryresult of music genres. [HttpGet] public ActionResult> GetMusicGenres( - [FromQuery] double? minCommunityRating, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? searchTerm, @@ -91,22 +74,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery] string? mediaTypes, - [FromQuery] string? genres, - [FromQuery] string? genreIds, - [FromQuery] string? officialRatings, - [FromQuery] string? tags, - [FromQuery] string? years, - [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery] ImageType[] enableImageTypes, - [FromQuery] string? person, - [FromQuery] string? personIds, - [FromQuery] string? personTypes, - [FromQuery] string? studios, - [FromQuery] string? studioIds, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, @@ -117,42 +87,22 @@ namespace Jellyfin.Api.Controllers var dtoOptions = new DtoOptions() .AddItemFields(fields) .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); + .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); - User? user = null; - BaseItem parentItem; + User? user = userId.HasValue && userId != Guid.Empty ? _userManager.GetUserById(userId.Value) : null; - if (userId.HasValue && !userId.Equals(Guid.Empty)) - { - user = _userManager.GetUserById(userId.Value); - parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId); - } - else - { - parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId); - } + var parentItem = _libraryManager.GetParentItem(parentId, userId); var query = new InternalItemsQuery(user) { ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true), IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true), - MediaTypes = RequestHelpers.Split(mediaTypes, ',', true), StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, - Tags = RequestHelpers.Split(tags, '|', true), - OfficialRatings = RequestHelpers.Split(officialRatings, '|', true), - Genres = RequestHelpers.Split(genres, '|', true), - GenreIds = RequestHelpers.GetGuids(genreIds), - StudioIds = RequestHelpers.GetGuids(studioIds), - Person = person, - PersonIds = RequestHelpers.GetGuids(personIds), - PersonTypes = RequestHelpers.Split(personTypes, ',', true), - Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(), - MinCommunityRating = minCommunityRating, DtoOptions = dtoOptions, SearchTerm = searchTerm, EnableTotalRecordCount = enableTotalRecordCount @@ -170,87 +120,10 @@ namespace Jellyfin.Api.Controllers } } - // Studios - if (!string.IsNullOrEmpty(studios)) - { - query.StudioIds = studios.Split('|') - .Select(i => - { - try - { - return _libraryManager.GetStudio(i); - } - catch - { - return null; - } - }).Where(i => i != null) - .Select(i => i!.Id) - .ToArray(); - } - - foreach (var filter in filters) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - var result = _libraryManager.GetMusicGenres(query); - var dtos = result.Items.Select(i => - { - var (baseItem, counts) = i; - var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); - - if (!string.IsNullOrWhiteSpace(includeItemTypes)) - { - dto.ChildCount = counts.ItemCount; - dto.ProgramCount = counts.ProgramCount; - dto.SeriesCount = counts.SeriesCount; - dto.EpisodeCount = counts.EpisodeCount; - dto.MovieCount = counts.MovieCount; - dto.TrailerCount = counts.TrailerCount; - dto.AlbumCount = counts.AlbumCount; - dto.SongCount = counts.SongCount; - dto.ArtistCount = counts.ArtistCount; - } - - return dto; - }); - - return new QueryResult - { - Items = dtos.ToArray(), - TotalRecordCount = result.TotalRecordCount - }; + var shouldIncludeItemTypes = !string.IsNullOrWhiteSpace(includeItemTypes); + return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); } /// From 8c461aff097d2521faed4d56e5822c8f4817bb49 Mon Sep 17 00:00:00 2001 From: SaddFox Date: Thu, 5 Nov 2020 12:53:24 +0000 Subject: [PATCH 149/203] Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sl/ --- .../Localization/Core/sl-SI.json | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index ff4b9e84fd..66681f0251 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -3,20 +3,20 @@ "AppDeviceValues": "Aplikacija: {0}, Naprava: {1}", "Application": "Aplikacija", "Artists": "Izvajalci", - "AuthenticationSucceededWithUserName": "{0} preverjanje pristnosti uspešno", + "AuthenticationSucceededWithUserName": "{0} se je uspešno prijavil", "Books": "Knjige", - "CameraImageUploadedFrom": "Nova fotografija je bila naložena z {0}", + "CameraImageUploadedFrom": "Nova fotografija je bila naložena iz {0}", "Channels": "Kanali", "ChapterNameValue": "Poglavje {0}", "Collections": "Zbirke", "DeviceOfflineWithName": "{0} je prekinil povezavo", "DeviceOnlineWithName": "{0} je povezan", - "FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}", + "FailedLoginAttemptWithUserName": "Neuspešen poskus prijave iz {0}", "Favorites": "Priljubljeno", "Folders": "Mape", "Genres": "Zvrsti", "HeaderAlbumArtists": "Izvajalci albuma", - "HeaderContinueWatching": "Nadaljuj gledanje", + "HeaderContinueWatching": "Nadaljuj z ogledom", "HeaderFavoriteAlbums": "Priljubljeni albumi", "HeaderFavoriteArtists": "Priljubljeni izvajalci", "HeaderFavoriteEpisodes": "Priljubljene epizode", @@ -32,23 +32,23 @@ "LabelIpAddressValue": "IP naslov: {0}", "LabelRunningTimeValue": "Čas trajanja: {0}", "Latest": "Najnovejše", - "MessageApplicationUpdated": "Jellyfin Server je bil posodobljen", - "MessageApplicationUpdatedTo": "Jellyfin Server je bil posodobljen na {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Oddelek nastavitve strežnika {0} je bil posodobljen", + "MessageApplicationUpdated": "Jellyfin strežnik je bil posodobljen", + "MessageApplicationUpdatedTo": "Jellyfin strežnik je bil posodobljen na {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Oddelek nastavitev {0} je bil posodobljen", "MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene", - "MixedContent": "Razne vsebine", + "MixedContent": "Mešane vsebine", "Movies": "Filmi", "Music": "Glasba", "MusicVideos": "Glasbeni videi", "NameInstallFailed": "{0} namestitev neuspešna", "NameSeasonNumber": "Sezona {0}", - "NameSeasonUnknown": "Season neznana", + "NameSeasonUnknown": "Neznana sezona", "NewVersionIsAvailable": "Nova različica Jellyfin strežnika je na voljo za prenos.", "NotificationOptionApplicationUpdateAvailable": "Posodobitev aplikacije je na voljo", "NotificationOptionApplicationUpdateInstalled": "Posodobitev aplikacije je bila nameščena", - "NotificationOptionAudioPlayback": "Predvajanje zvoka začeto", - "NotificationOptionAudioPlaybackStopped": "Predvajanje zvoka zaustavljeno", - "NotificationOptionCameraImageUploaded": "Posnetek kamere naložen", + "NotificationOptionAudioPlayback": "Predvajanje zvoka se je začelo", + "NotificationOptionAudioPlaybackStopped": "Predvajanje zvoka se je ustavilo", + "NotificationOptionCameraImageUploaded": "Fotografija naložena", "NotificationOptionInstallationFailed": "Namestitev neuspešna", "NotificationOptionNewLibraryContent": "Nove vsebine dodane", "NotificationOptionPluginError": "Napaka dodatka", @@ -56,41 +56,41 @@ "NotificationOptionPluginUninstalled": "Dodatek odstranjen", "NotificationOptionPluginUpdateInstalled": "Posodobitev dodatka nameščena", "NotificationOptionServerRestartRequired": "Potreben je ponovni zagon strežnika", - "NotificationOptionTaskFailed": "Razporejena naloga neuspešna", + "NotificationOptionTaskFailed": "Načrtovano opravilo neuspešno", "NotificationOptionUserLockedOut": "Uporabnik zaklenjen", "NotificationOptionVideoPlayback": "Predvajanje videa se je začelo", "NotificationOptionVideoPlaybackStopped": "Predvajanje videa se je ustavilo", "Photos": "Fotografije", "Playlists": "Seznami predvajanja", - "Plugin": "Plugin", + "Plugin": "Dodatek", "PluginInstalledWithName": "{0} je bil nameščen", "PluginUninstalledWithName": "{0} je bil odstranjen", "PluginUpdatedWithName": "{0} je bil posodobljen", - "ProviderValue": "Provider: {0}", + "ProviderValue": "Ponudnik: {0}", "ScheduledTaskFailedWithName": "{0} ni uspelo", "ScheduledTaskStartedWithName": "{0} začeto", "ServerNameNeedsToBeRestarted": "{0} mora biti ponovno zagnan", "Shows": "Serije", "Songs": "Pesmi", - "StartupEmbyServerIsLoading": "Jellyfin Server se nalaga. Poskusi ponovno kasneje.", + "StartupEmbyServerIsLoading": "Jellyfin strežnik se zaganja. Poskusite ponovno kasneje.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Neuspešen prenos podnapisov iz {0} za {1}", "Sync": "Sinhroniziraj", - "System": "System", + "System": "Sistem", "TvShows": "TV serije", - "User": "User", + "User": "Uporabnik", "UserCreatedWithName": "Uporabnik {0} je bil ustvarjen", "UserDeletedWithName": "Uporabnik {0} je bil izbrisan", "UserDownloadingItemWithValues": "{0} prenaša {1}", "UserLockedOutWithName": "Uporabnik {0} je bil zaklenjen", "UserOfflineFromDevice": "{0} je prekinil povezavo z {1}", - "UserOnlineFromDevice": "{0} je aktiven iz {1}", + "UserOnlineFromDevice": "{0} je aktiven na {1}", "UserPasswordChangedWithName": "Geslo za uporabnika {0} je bilo spremenjeno", "UserPolicyUpdatedWithName": "Pravilnik uporabe je bil posodobljen za uporabnika {0}", "UserStartedPlayingItemWithValues": "{0} predvaja {1} na {2}", "UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}", "ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici", - "ValueSpecialEpisodeName": "Poseben - {0}", + "ValueSpecialEpisodeName": "Posebna - {0}", "VersionNumber": "Različica {0}", "TaskDownloadMissingSubtitles": "Prenesi manjkajoče podnapise", "TaskRefreshChannelsDescription": "Osveži podatke spletnih kanalov.", @@ -102,7 +102,7 @@ "TaskRefreshPeopleDescription": "Osveži metapodatke za igralce in režiserje v vaši knjižnici.", "TaskRefreshPeople": "Osveži osebe", "TaskCleanLogsDescription": "Izbriše dnevniške datoteke starejše od {0} dni.", - "TaskCleanLogs": "Počisti mapo dnevnika", + "TaskCleanLogs": "Počisti mapo dnevnikov", "TaskRefreshLibraryDescription": "Preišče vašo knjižnico za nove datoteke in osveži metapodatke.", "TaskRefreshLibrary": "Preišči knjižnico predstavnosti", "TaskRefreshChapterImagesDescription": "Ustvari sličice za poglavja videoposnetkov.", From 3c6cbb6161eaa504df9fea28830b026a3a9001cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Thu, 5 Nov 2020 15:00:18 +0000 Subject: [PATCH 150/203] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index b34fad066a..fb31b01ff8 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -113,5 +113,7 @@ "TasksChannelsCategory": "Internetové kanály", "TasksApplicationCategory": "Aplikace", "TasksLibraryCategory": "Knihovna", - "TasksMaintenanceCategory": "Údržba" + "TasksMaintenanceCategory": "Údržba", + "TaskCleanActivityLogDescription": "Smazat záznamy o aktivitě, které jsou starší než zadaná doba.", + "TaskCleanActivityLog": "Smazat záznam aktivity" } From 3756611d0155b77d1a17be63e71a3181904d4bc3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 5 Nov 2020 13:23:22 -0700 Subject: [PATCH 151/203] Add /Users/Me endpoint --- Jellyfin.Api/Controllers/UserController.cs | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 7b0897bfb9..0f7c25d0e4 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -530,6 +530,33 @@ namespace Jellyfin.Api.Controllers return result; } + /// + /// Gets the user based on auth token. + /// + /// User returned. + /// Token is not owned by a user. + /// A for the authenticated user. + [HttpGet("Me")] + [Authorize(Policy = Policies.DefaultAuthorization)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public ActionResult GetCurrentUser() + { + var userId = ClaimHelpers.GetUserId(Request.HttpContext.User); + if (userId == null) + { + return BadRequest(); + } + + var user = _userManager.GetUserById(userId.Value); + if (user == null) + { + return BadRequest(); + } + + return _userManager.GetUserDto(user); + } + private IEnumerable Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork) { var users = _userManager.Users; From eeb3177cc3d8911b0d06dadb6f7d03600d11dac1 Mon Sep 17 00:00:00 2001 From: public_yusuke Date: Fri, 6 Nov 2020 02:49:23 +0000 Subject: [PATCH 152/203] 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 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index 35004f0eb4..02bf8496ff 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -96,7 +96,7 @@ "TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータをリフレッシュします。", "TaskRefreshLibrary": "メディアライブラリのスキャン", "TaskCleanCacheDescription": "不要なキャッシュを消去します。", - "TaskCleanCache": "キャッシュの掃除", + "TaskCleanCache": "キャッシュを消去", "TasksChannelsCategory": "ネットチャンネル", "TasksApplicationCategory": "アプリケーション", "TasksLibraryCategory": "ライブラリ", @@ -112,5 +112,7 @@ "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。", "TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。", "TaskRefreshChapterImages": "チャプター画像を抽出する", - "TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする" + "TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする", + "TaskCleanActivityLogDescription": "設定された期間よりも古いアクティビティの履歴を削除します。", + "TaskCleanActivityLog": "アクティビティの履歴を消去" } From 6e6ecbc988286108c691578a526f1f29aef4a229 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 6 Nov 2020 08:34:20 +0100 Subject: [PATCH 153/203] Make /MusicGenres obsolete --- Jellyfin.Api/Controllers/GenresController.cs | 1 + .../Controllers/MusicGenresController.cs | 82 ------------------- .../ApiServiceCollectionExtensions.cs | 1 + .../Filters/ObsoleteRoutesFilter.cs | 22 +++++ 4 files changed, 24 insertions(+), 82 deletions(-) create mode 100644 Jellyfin.Server/Filters/ObsoleteRoutesFilter.cs diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 4e47658b06..94c1b4b9ee 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -66,6 +66,7 @@ namespace Jellyfin.Api.Controllers /// Genres returned. /// An containing the queryresult of genres. [HttpGet] + [HttpGet("/MusicGenres", Name = "GetMusicGenres-Deprecated")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetGenres( [FromQuery] int? startIndex, diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 24075b9052..28295f0cbf 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -44,88 +44,6 @@ namespace Jellyfin.Api.Controllers _dtoService = dtoService; } - /// - /// Gets all music genres from a given item, folder, or the entire library. - /// - /// Optional. The record index to start at. All items with a lower index will be dropped from the results. - /// Optional. The maximum number of records to return. - /// The search term. - /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. - /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. - /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. - /// Optional filter by items that are marked as favorite, or not. - /// Optional, the max number of images to return, per image type. - /// Optional. The image types to include in the output. - /// User id. - /// Optional filter by items whose name is sorted equally or greater than a given input string. - /// Optional filter by items whose name is sorted equally than a given input string. - /// Optional filter by items whose name is equally or lesser than a given input string. - /// Optional, include image information in output. - /// Optional. Include total record count. - /// Music genres returned. - /// An containing the queryresult of music genres. - [HttpGet] - public ActionResult> GetMusicGenres( - [FromQuery] int? startIndex, - [FromQuery] int? limit, - [FromQuery] string? searchTerm, - [FromQuery] string? parentId, - [FromQuery] string? fields, - [FromQuery] string? excludeItemTypes, - [FromQuery] string? includeItemTypes, - [FromQuery] bool? isFavorite, - [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, - [FromQuery] Guid? userId, - [FromQuery] string? nameStartsWithOrGreater, - [FromQuery] string? nameStartsWith, - [FromQuery] string? nameLessThan, - [FromQuery] bool? enableImages = true, - [FromQuery] bool enableTotalRecordCount = true) - { - var dtoOptions = new DtoOptions() - .AddItemFields(fields) - .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); - - User? user = userId.HasValue && userId != Guid.Empty ? _userManager.GetUserById(userId.Value) : null; - - var parentItem = _libraryManager.GetParentItem(parentId, userId); - - var query = new InternalItemsQuery(user) - { - ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true), - IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true), - StartIndex = startIndex, - Limit = limit, - IsFavorite = isFavorite, - NameLessThan = nameLessThan, - NameStartsWith = nameStartsWith, - NameStartsWithOrGreater = nameStartsWithOrGreater, - DtoOptions = dtoOptions, - SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount - }; - - if (!string.IsNullOrWhiteSpace(parentId)) - { - if (parentItem is Folder) - { - query.AncestorIds = new[] { new Guid(parentId) }; - } - else - { - query.ItemIds = new[] { new Guid(parentId) }; - } - } - - var result = _libraryManager.GetMusicGenres(query); - - var shouldIncludeItemTypes = !string.IsNullOrWhiteSpace(includeItemTypes); - return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); - } - /// /// Gets a music genre, by name. /// diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index e180d0cd7a..f0270e9935 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -260,6 +260,7 @@ namespace Jellyfin.Server.Extensions c.AddSwaggerTypeMappings(); c.OperationFilter(); + c.OperationFilter(); c.DocumentFilter(); }); } diff --git a/Jellyfin.Server/Filters/ObsoleteRoutesFilter.cs b/Jellyfin.Server/Filters/ObsoleteRoutesFilter.cs new file mode 100644 index 0000000000..34cd72752a --- /dev/null +++ b/Jellyfin.Server/Filters/ObsoleteRoutesFilter.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters +{ + /// + public class ObsoleteRoutesFilter : IOperationFilter + { + /// + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor actionDescriptor + && !string.IsNullOrEmpty(actionDescriptor.AttributeRouteInfo.Name) + && actionDescriptor.AttributeRouteInfo.Name.EndsWith("deprecated", StringComparison.OrdinalIgnoreCase)) + { + operation.Deprecated = true; + } + } + } +} From de52c8a497c288b8f7a807df806530a099b5ab0e Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 6 Nov 2020 09:35:23 +0100 Subject: [PATCH 154/203] Revert "Make /MusicGenres obsolete" --- Jellyfin.Api/Controllers/GenresController.cs | 1 - .../Controllers/MusicGenresController.cs | 82 +++++++++++++++++++ .../ApiServiceCollectionExtensions.cs | 1 - .../Filters/ObsoleteRoutesFilter.cs | 22 ----- 4 files changed, 82 insertions(+), 24 deletions(-) delete mode 100644 Jellyfin.Server/Filters/ObsoleteRoutesFilter.cs diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 94c1b4b9ee..4e47658b06 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -66,7 +66,6 @@ namespace Jellyfin.Api.Controllers /// Genres returned. /// An containing the queryresult of genres. [HttpGet] - [HttpGet("/MusicGenres", Name = "GetMusicGenres-Deprecated")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetGenres( [FromQuery] int? startIndex, diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 28295f0cbf..24075b9052 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -44,6 +44,88 @@ namespace Jellyfin.Api.Controllers _dtoService = dtoService; } + /// + /// Gets all music genres from a given item, folder, or the entire library. + /// + /// Optional. The record index to start at. All items with a lower index will be dropped from the results. + /// Optional. The maximum number of records to return. + /// The search term. + /// Specify this to localize the search to a specific item or folder. Omit to use the root. + /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. + /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. + /// Optional filter by items that are marked as favorite, or not. + /// Optional, the max number of images to return, per image type. + /// Optional. The image types to include in the output. + /// User id. + /// Optional filter by items whose name is sorted equally or greater than a given input string. + /// Optional filter by items whose name is sorted equally than a given input string. + /// Optional filter by items whose name is equally or lesser than a given input string. + /// Optional, include image information in output. + /// Optional. Include total record count. + /// Music genres returned. + /// An containing the queryresult of music genres. + [HttpGet] + public ActionResult> GetMusicGenres( + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string? searchTerm, + [FromQuery] string? parentId, + [FromQuery] string? fields, + [FromQuery] string? excludeItemTypes, + [FromQuery] string? includeItemTypes, + [FromQuery] bool? isFavorite, + [FromQuery] int? imageTypeLimit, + [FromQuery] ImageType[] enableImageTypes, + [FromQuery] Guid? userId, + [FromQuery] string? nameStartsWithOrGreater, + [FromQuery] string? nameStartsWith, + [FromQuery] string? nameLessThan, + [FromQuery] bool? enableImages = true, + [FromQuery] bool enableTotalRecordCount = true) + { + var dtoOptions = new DtoOptions() + .AddItemFields(fields) + .AddClientFields(Request) + .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); + + User? user = userId.HasValue && userId != Guid.Empty ? _userManager.GetUserById(userId.Value) : null; + + var parentItem = _libraryManager.GetParentItem(parentId, userId); + + var query = new InternalItemsQuery(user) + { + ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true), + IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true), + StartIndex = startIndex, + Limit = limit, + IsFavorite = isFavorite, + NameLessThan = nameLessThan, + NameStartsWith = nameStartsWith, + NameStartsWithOrGreater = nameStartsWithOrGreater, + DtoOptions = dtoOptions, + SearchTerm = searchTerm, + EnableTotalRecordCount = enableTotalRecordCount + }; + + if (!string.IsNullOrWhiteSpace(parentId)) + { + if (parentItem is Folder) + { + query.AncestorIds = new[] { new Guid(parentId) }; + } + else + { + query.ItemIds = new[] { new Guid(parentId) }; + } + } + + var result = _libraryManager.GetMusicGenres(query); + + var shouldIncludeItemTypes = !string.IsNullOrWhiteSpace(includeItemTypes); + return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); + } + /// /// Gets a music genre, by name. /// diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index f0270e9935..e180d0cd7a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -260,7 +260,6 @@ namespace Jellyfin.Server.Extensions c.AddSwaggerTypeMappings(); c.OperationFilter(); - c.OperationFilter(); c.DocumentFilter(); }); } diff --git a/Jellyfin.Server/Filters/ObsoleteRoutesFilter.cs b/Jellyfin.Server/Filters/ObsoleteRoutesFilter.cs deleted file mode 100644 index 34cd72752a..0000000000 --- a/Jellyfin.Server/Filters/ObsoleteRoutesFilter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Jellyfin.Server.Filters -{ - /// - public class ObsoleteRoutesFilter : IOperationFilter - { - /// - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor actionDescriptor - && !string.IsNullOrEmpty(actionDescriptor.AttributeRouteInfo.Name) - && actionDescriptor.AttributeRouteInfo.Name.EndsWith("deprecated", StringComparison.OrdinalIgnoreCase)) - { - operation.Deprecated = true; - } - } - } -} From b693c52fe7f2fda6ea009c3ee8c032189369d896 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 6 Nov 2020 09:42:59 +0100 Subject: [PATCH 155/203] Make /MusicGenres obsolete --- Jellyfin.Api/Controllers/MusicGenresController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 24075b9052..e105befe8e 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -66,6 +66,7 @@ namespace Jellyfin.Api.Controllers /// Music genres returned. /// An containing the queryresult of music genres. [HttpGet] + [Obsolete("Use GetGenres instead")] public ActionResult> GetMusicGenres( [FromQuery] int? startIndex, [FromQuery] int? limit, From b21919c7f40770c909a0fc217bf2a326397f84f7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 6 Nov 2020 16:15:30 +0100 Subject: [PATCH 156/203] Minor perf improvements --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 4 ++-- Emby.Dlna/Didl/DidlBuilder.cs | 2 +- Emby.Dlna/DlnaManager.cs | 6 +++--- Emby.Dlna/Eventing/DlnaEventManager.cs | 2 +- Emby.Dlna/Main/DlnaEntryPoint.cs | 4 ++-- Emby.Dlna/PlayTo/PlayToController.cs | 4 ++-- Emby.Notifications/NotificationEntryPoint.cs | 5 ++++- .../Channels/ChannelManager.cs | 17 ++++++---------- .../Controllers/InstantMixController.cs | 4 ++-- .../Controllers/UniversalAudioController.cs | 20 +++++++++++-------- Jellyfin.Api/Helpers/SimilarItemsHelper.cs | 4 ++-- .../IServerApplicationHost.cs | 9 +++++---- 12 files changed, 42 insertions(+), 39 deletions(-) diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 2991861129..5f25b8cdc0 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -1346,8 +1346,8 @@ namespace Emby.Dlna.ContentDirectory { if (id.StartsWith(name + "_", StringComparison.OrdinalIgnoreCase)) { - stubType = (StubType)Enum.Parse(typeof(StubType), name, true); - id = id.Split(new[] { '_' }, 2)[1]; + stubType = Enum.Parse(name, true); + id = id.Split('_', 2)[1]; break; } diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 5b8a89d8f3..abaf522bca 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -123,7 +123,7 @@ namespace Emby.Dlna.Didl { foreach (var att in profile.XmlRootAttributes) { - var parts = att.Name.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + var parts = att.Name.Split(':', StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 2) { writer.WriteAttributeString(parts[0], parts[1], null, att.Value); diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 1807ac6a13..069400833e 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -383,9 +383,9 @@ namespace Emby.Dlna continue; } - var filename = Path.GetFileName(name).Substring(namespaceName.Length); - - var path = Path.Combine(systemProfilesPath, filename); + var path = Path.Join( + systemProfilesPath, + Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length)); using (var stream = _assembly.GetManifestResourceStream(name)) { diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index 7d8da86ef9..770d56c30f 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -168,7 +168,7 @@ namespace Emby.Dlna.Eventing builder.Append(""); - using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl); + using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl); options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml); options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType); options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange"); diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 40c2cc0e0a..f8a00efaca 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -257,9 +257,10 @@ namespace Emby.Dlna.Main private async Task RegisterServerEndpoints() { - var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false); + var addresses = await _appHost.GetLocalIpAddresses().ConfigureAwait(false); var udn = CreateUuid(_appHost.SystemId); + var descriptorUri = "/dlna/" + udn + "/description.xml"; foreach (var address in addresses) { @@ -279,7 +280,6 @@ namespace Emby.Dlna.Main _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); - var descriptorUri = "/dlna/" + udn + "/description.xml"; var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri); var device = new SsdpRootDevice diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index a5b8e2b3ce..c07c8aefa6 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -326,7 +326,7 @@ namespace Emby.Dlna.PlayTo public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) { - _logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand); + _logger.LogDebug("{0} - Received PlayRequest: {1}", _session.DeviceName, command.PlayCommand); var user = command.ControllingUserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(command.ControllingUserId); @@ -339,7 +339,7 @@ namespace Emby.Dlna.PlayTo var startIndex = command.StartIndex ?? 0; if (startIndex > 0) { - items = items.Skip(startIndex).ToList(); + items = items.GetRange(startIndex, items.Count - startIndex); } var playlist = new List(); diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs index ded22d26cc..7116d52b11 100644 --- a/Emby.Notifications/NotificationEntryPoint.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -209,7 +209,10 @@ namespace Emby.Notifications _libraryUpdateTimer = null; } - items = items.Take(10).ToList(); + if (items.Count > 10) + { + items = items.GetRange(0, 10); + } foreach (var item in items) { diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index db44bf4898..19045b72b4 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -250,21 +250,16 @@ namespace Emby.Server.Implementations.Channels var all = channels; var totalCount = all.Count; - if (query.StartIndex.HasValue) + if (query.StartIndex.HasValue || query.Limit.HasValue) { - all = all.Skip(query.StartIndex.Value).ToList(); + int startIndex = query.StartIndex ?? 0; + int count = query.Limit == null ? totalCount - startIndex : Math.Min(query.Limit.Value, totalCount - startIndex); + all = all.GetRange(startIndex, count); } - if (query.Limit.HasValue) - { - all = all.Take(query.Limit.Value).ToList(); - } - - var returnItems = all.ToArray(); - if (query.RefreshLatestChannelItems) { - foreach (var item in returnItems) + foreach (var item in all) { RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult(); } @@ -272,7 +267,7 @@ namespace Emby.Server.Implementations.Channels return new QueryResult { - Items = returnItems, + Items = all, TotalRecordCount = totalCount }; } diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index e6e6b3e70e..7682ceff3e 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -316,9 +316,9 @@ namespace Jellyfin.Api.Controllers TotalRecordCount = list.Count }; - if (limit.HasValue) + if (limit.HasValue && limit > list.Count) { - list = list.Take(limit.Value).ToList(); + list = list.GetRange(0, limit.Value); } var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user); diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index a219a74cf8..924ae04778 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -268,20 +268,24 @@ namespace Jellyfin.Api.Controllers { var deviceProfile = new DeviceProfile(); - var directPlayProfiles = new List(); - var containers = RequestHelpers.Split(container, ',', true); - - foreach (var cont in containers) + int len = containers.Length; + var directPlayProfiles = new DirectPlayProfile[len]; + for (int i = 0; i < len; i++) { - var parts = RequestHelpers.Split(cont, '|', true); + var parts = RequestHelpers.Split(containers[i], '|', true); - var audioCodecs = parts.Length == 1 ? null : string.Join(",", parts.Skip(1).ToArray()); + var audioCodecs = parts.Length == 1 ? null : string.Join(',', parts.Skip(1)); - directPlayProfiles.Add(new DirectPlayProfile { Type = DlnaProfileType.Audio, Container = parts[0], AudioCodec = audioCodecs }); + directPlayProfiles[i] = new DirectPlayProfile + { + Type = DlnaProfileType.Audio, + Container = parts[0], + AudioCodec = audioCodecs + }; } - deviceProfile.DirectPlayProfiles = directPlayProfiles.ToArray(); + deviceProfile.DirectPlayProfiles = directPlayProfiles; deviceProfile.TranscodingProfiles = new[] { diff --git a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs index b922e76cfd..f4b654ef01 100644 --- a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs +++ b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs @@ -50,9 +50,9 @@ namespace Jellyfin.Api.Helpers var returnItems = items; - if (limit.HasValue) + if (limit.HasValue && limit > returnItems.Count) { - returnItems = returnItems.Take(limit.Value).ToList(); + returnItems = returnItems.GetRange(0, limit.Value); } var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index cfad17fb72..649b0eaec8 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -56,10 +56,11 @@ namespace MediaBrowser.Controller /// /// Gets the system info. /// + /// A cancellation token that can be used to cancel the task. /// SystemInfo. - Task GetSystemInfo(CancellationToken cancellationToken); + Task GetSystemInfo(CancellationToken cancellationToken = default); - Task GetPublicSystemInfo(CancellationToken cancellationToken); + Task GetPublicSystemInfo(CancellationToken cancellationToken = default); /// /// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request @@ -67,7 +68,7 @@ namespace MediaBrowser.Controller /// /// A cancellation token that can be used to cancel the task. /// A list containing all the local IP addresses of the server. - Task> GetLocalIpAddresses(CancellationToken cancellationToken); + Task> GetLocalIpAddresses(CancellationToken cancellationToken = default); /// /// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured @@ -75,7 +76,7 @@ namespace MediaBrowser.Controller /// /// A cancellation token that can be used to cancel the task. /// The server URL. - Task GetLocalApiUrl(CancellationToken cancellationToken); + Task GetLocalApiUrl(CancellationToken cancellationToken = default); /// /// Gets a localhost URL that can be used to access the API using the loop-back IP address (127.0.0.1) From e6480066b13d5c1aed73bcec291c3f2f35f26506 Mon Sep 17 00:00:00 2001 From: lelamamalgache Date: Fri, 6 Nov 2020 14:43:37 +0000 Subject: [PATCH 157/203] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index f4ca8575a1..cc9243f37b 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -113,5 +113,7 @@ "TaskCleanCache": "Vider le répertoire cache", "TasksApplicationCategory": "Application", "TasksLibraryCategory": "Bibliothèque", - "TasksMaintenanceCategory": "Maintenance" + "TasksMaintenanceCategory": "Maintenance", + "TaskCleanActivityLogDescription": "Supprime les entrées du journal d'activité antérieures à l'âge configuré.", + "TaskCleanActivityLog": "Nettoyer le journal d'activité" } From 134fd0d9604ab2108e5008205ede3444aa1a7610 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 6 Nov 2020 16:39:41 +0100 Subject: [PATCH 158/203] > -> < --- Jellyfin.Api/Controllers/InstantMixController.cs | 2 +- Jellyfin.Api/Helpers/SimilarItemsHelper.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 7682ceff3e..7f8a2be12e 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -316,7 +316,7 @@ namespace Jellyfin.Api.Controllers TotalRecordCount = list.Count }; - if (limit.HasValue && limit > list.Count) + if (limit.HasValue && limit < list.Count) { list = list.GetRange(0, limit.Value); } diff --git a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs index f4b654ef01..6b06f87cd7 100644 --- a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs +++ b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs @@ -50,7 +50,7 @@ namespace Jellyfin.Api.Helpers var returnItems = items; - if (limit.HasValue && limit > returnItems.Count) + if (limit.HasValue && limit < returnItems.Count) { returnItems = returnItems.GetRange(0, limit.Value); } From ca40eff83dd9b7e186574e70f05775f744b821df Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Fri, 6 Nov 2020 12:00:00 -0500 Subject: [PATCH 159/203] Restore missing targetFolder --- .ci/azure-pipelines-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 67aac45c9d..852594e67c 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -63,6 +63,7 @@ jobs: sshEndpoint: repository sourceFolder: '$(Build.SourcesDirectory)/deployment/dist' contents: '**' + targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)' - job: OpenAPISpec dependsOn: Test From e2ab7af0336db05474e1ef2279299763a20a4362 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Fri, 6 Nov 2020 12:32:06 -0500 Subject: [PATCH 160/203] Remove spurious argument to nohup --- .ci/azure-pipelines-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 67aac45c9d..259d71e4ce 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -166,7 +166,7 @@ jobs: inputs: sshEndpoint: repository runOptions: 'commands' - commands: sudo nohup -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable & + commands: sudo nohup /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable & - task: SSH@0 displayName: 'Update Stable Repository' @@ -175,7 +175,7 @@ jobs: inputs: sshEndpoint: repository runOptions: 'commands' - commands: sudo nohup -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) & + commands: sudo nohup /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) & - job: PublishNuget displayName: 'Publish NuGet packages' From a4e67dac80e932f5f80b3044081e0c18ca8b6d1d Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Fri, 6 Nov 2020 13:00:59 -0500 Subject: [PATCH 161/203] Reverse order of sudo and nohup Otherwise I'm forced to allow `sudo nohup` globally which is bad news bears. --- .ci/azure-pipelines-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 259d71e4ce..326f15b14b 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -166,7 +166,7 @@ jobs: inputs: sshEndpoint: repository runOptions: 'commands' - commands: sudo nohup /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable & + commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable & - task: SSH@0 displayName: 'Update Stable Repository' @@ -175,7 +175,7 @@ jobs: inputs: sshEndpoint: repository runOptions: 'commands' - commands: sudo nohup /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) & + commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) & - job: PublishNuget displayName: 'Publish NuGet packages' From 4a5651e5b2fb8e483cabae4443f327f1cdf70b92 Mon Sep 17 00:00:00 2001 From: JB Date: Fri, 6 Nov 2020 18:52:12 +0000 Subject: [PATCH 162/203] Translated using Weblate (Korean) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/ --- Emby.Server.Implementations/Localization/Core/ko.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index fb01e46451..2857461794 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -113,5 +113,7 @@ "TaskCleanCacheDescription": "시스템에서 더 이상 필요하지 않은 캐시 파일을 삭제합니다.", "TaskCleanCache": "캐시 폴더 청소", "TasksChannelsCategory": "인터넷 채널", - "TasksLibraryCategory": "라이브러리" + "TasksLibraryCategory": "라이브러리", + "TaskCleanActivityLogDescription": "구성된 기간보다 오래된 활동내역 삭제", + "TaskCleanActivityLog": "활동내역청소" } From fed37630db75327eee3ded3b200f105017e6de6c Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 6 Nov 2020 13:00:14 -0700 Subject: [PATCH 163/203] Add x-jellyfin-version to openapi spec --- .../ApiServiceCollectionExtensions.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index e180d0cd7a..cc98955df7 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Reflection; +using Emby.Server.Implementations; using Jellyfin.Api.Auth; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Api.Auth.DownloadPolicy; @@ -27,6 +28,8 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes; @@ -209,7 +212,19 @@ namespace Jellyfin.Server.Extensions { return serviceCollection.AddSwaggerGen(c => { - c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + c.SwaggerDoc("api-docs", new OpenApiInfo + { + Title = "Jellyfin API", + Version = "v1", + Extensions = new Dictionary + { + { + "x-jellyfin-version", + new OpenApiString(typeof(ApplicationHost).Assembly.GetName().Version?.ToString()) + } + } + }); + c.AddSecurityDefinition(AuthenticationSchemes.CustomAuthentication, new OpenApiSecurityScheme { Type = SecuritySchemeType.ApiKey, From 68f8ff678a08c4efcf125932962f130f2e9235b8 Mon Sep 17 00:00:00 2001 From: JB Date: Fri, 6 Nov 2020 20:01:39 +0000 Subject: [PATCH 164/203] Translated using Weblate (Korean) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/ --- Emby.Server.Implementations/Localization/Core/ko.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index 2857461794..b8b39833c8 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -27,7 +27,7 @@ "HeaderRecordingGroups": "녹화 그룹", "HomeVideos": "홈 비디오", "Inherit": "상속", - "ItemAddedWithName": "{0}가 라이브러리에 추가됨", + "ItemAddedWithName": "{0}가 라이브러리에 추가되었습니다", "ItemRemovedWithName": "{0}가 라이브러리에서 제거됨", "LabelIpAddressValue": "IP 주소: {0}", "LabelRunningTimeValue": "상영 시간: {0}", @@ -114,6 +114,6 @@ "TaskCleanCache": "캐시 폴더 청소", "TasksChannelsCategory": "인터넷 채널", "TasksLibraryCategory": "라이브러리", - "TaskCleanActivityLogDescription": "구성된 기간보다 오래된 활동내역 삭제", + "TaskCleanActivityLogDescription": "구성된 기간보다 오래된 활동내역 삭제.", "TaskCleanActivityLog": "활동내역청소" } From a0699b686851db4fe9865d11eb4c58e72592883e Mon Sep 17 00:00:00 2001 From: Ludovico Besana Date: Sat, 7 Nov 2020 01:15:53 +0000 Subject: [PATCH 165/203] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 0a6238578d..9e37ddc27c 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -113,5 +113,7 @@ "TasksChannelsCategory": "Canali su Internet", "TasksApplicationCategory": "Applicazione", "TasksLibraryCategory": "Libreria", - "TasksMaintenanceCategory": "Manutenzione" + "TasksMaintenanceCategory": "Manutenzione", + "TaskCleanActivityLog": "Attività di Registro Completate", + "TaskCleanActivityLogDescription": "Elimina gli inserimenti nel registro delle attività più vecchie dell’età configurata." } From 03d5a3d8e76154b2368a0e08c4a89b807eb05b8f Mon Sep 17 00:00:00 2001 From: tomwaits00 Date: Sat, 7 Nov 2020 01:46:48 +0000 Subject: [PATCH 166/203] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/ --- Emby.Server.Implementations/Localization/Core/tr.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 1e5f2cf198..818b57c7f6 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -8,7 +8,7 @@ "CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi", "Channels": "Kanallar", "ChapterNameValue": "Bölüm {0}", - "Collections": "Koleksiyonlar", + "Collections": "Koleksiyon", "DeviceOfflineWithName": "{0} bağlantısı kesildi", "DeviceOnlineWithName": "{0} bağlı", "FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu", @@ -23,7 +23,7 @@ "HeaderFavoriteShows": "Favori Diziler", "HeaderFavoriteSongs": "Favori Şarkılar", "HeaderLiveTV": "Canlı TV", - "HeaderNextUp": "Sonraki hafta", + "HeaderNextUp": "Gelecek Hafta", "HeaderRecordingGroups": "Kayıt Grupları", "HomeVideos": "Ev videoları", "Inherit": "Devral", @@ -113,5 +113,6 @@ "TaskRefreshLibrary": "Medya Kütüphanesini Tara", "TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.", "TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar", - "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler." + "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.", + "TaskCleanActivityLog": "İşlem Günlüğünü Temizle" } From 549d0bc27b4194a0508d29ee5c81ff551cd069a5 Mon Sep 17 00:00:00 2001 From: Florian Schmidt Date: Sat, 7 Nov 2020 07:53:50 +0000 Subject: [PATCH 167/203] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/ --- Emby.Server.Implementations/Localization/Core/de.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 97ad1694a6..c81de8218f 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -113,5 +113,7 @@ "TasksChannelsCategory": "Internet Kanäle", "TasksApplicationCategory": "Anwendung", "TasksLibraryCategory": "Bibliothek", - "TasksMaintenanceCategory": "Wartung" + "TasksMaintenanceCategory": "Wartung", + "TaskCleanActivityLogDescription": "Löscht Aktivitätsprotokolleinträge, die älter als das konfigurierte Alter sind.", + "TaskCleanActivityLog": "Aktivitätsprotokoll aufräumen" } From 6b5ba0f64ad20f7395afdb4504a8284c7a214550 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Sat, 7 Nov 2020 07:23:08 +0000 Subject: [PATCH 168/203] 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, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 95b93afb82..c0db2cf7f5 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -113,5 +113,7 @@ "TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).", "TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.", "TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.", - "TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе." + "TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе.", + "TaskCleanActivityLogDescription": "Удаляет записи журнала активности старше установленного возраста.", + "TaskCleanActivityLog": "Очистить журнал активности" } From c6ff56a230c2de4a983374898647fca2f7414ab9 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 7 Nov 2020 18:59:32 +0000 Subject: [PATCH 169/203] Update BaseControlHandler.cs --- Emby.Dlna/Service/BaseControlHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 4b0bbba969..198852ec17 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -169,6 +169,7 @@ namespace Emby.Dlna.Service var result = new ControlRequestInfo(localName, namespaceURI); using var subReader = reader.ReadSubtree(); await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); + return result; } else { From 7f4a3219eac136749546bebe0f398eb1e15c8311 Mon Sep 17 00:00:00 2001 From: Tomislav Date: Sat, 7 Nov 2020 20:23:16 +0000 Subject: [PATCH 170/203] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/ --- .../Localization/Core/hr.json | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index a3c936240d..712ffde699 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -5,13 +5,13 @@ "Artists": "Izvođači", "AuthenticationSucceededWithUserName": "{0} uspješno ovjerena", "Books": "Knjige", - "CameraImageUploadedFrom": "Nova fotografija sa kamere je uploadana iz {0}", + "CameraImageUploadedFrom": "Nova fotografija sa kamere je učitana iz {0}", "Channels": "Kanali", "ChapterNameValue": "Poglavlje {0}", "Collections": "Kolekcije", - "DeviceOfflineWithName": "{0} se odspojilo", - "DeviceOnlineWithName": "{0} je spojeno", - "FailedLoginAttemptWithUserName": "Neuspjeli pokušaj prijave za {0}", + "DeviceOfflineWithName": "{0} je prekinuo vezu", + "DeviceOnlineWithName": "{0} je povezan", + "FailedLoginAttemptWithUserName": "Neuspjeli pokušaj prijave od {0}", "Favorites": "Favoriti", "Folders": "Mape", "Genres": "Žanrovi", @@ -23,19 +23,19 @@ "HeaderFavoriteShows": "Omiljene serije", "HeaderFavoriteSongs": "Omiljene pjesme", "HeaderLiveTV": "TV uživo", - "HeaderNextUp": "Sljedeće je", + "HeaderNextUp": "Slijedi", "HeaderRecordingGroups": "Grupa snimka", - "HomeVideos": "Kućni videi", + "HomeVideos": "Kućni video", "Inherit": "Naslijedi", "ItemAddedWithName": "{0} je dodano u biblioteku", - "ItemRemovedWithName": "{0} je uklonjen iz biblioteke", + "ItemRemovedWithName": "{0} je uklonjeno iz biblioteke", "LabelIpAddressValue": "IP adresa: {0}", "LabelRunningTimeValue": "Vrijeme rada: {0}", "Latest": "Najnovije", - "MessageApplicationUpdated": "Jellyfin Server je ažuriran", - "MessageApplicationUpdatedTo": "Jellyfin Server je ažuriran na {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Odjeljak postavka servera {0} je ažuriran", - "MessageServerConfigurationUpdated": "Postavke servera su ažurirane", + "MessageApplicationUpdated": "Jellyfin server je ažuriran", + "MessageApplicationUpdatedTo": "Jellyfin server je ažuriran na {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Dio konfiguracije servera {0} je ažuriran", + "MessageServerConfigurationUpdated": "Konfiguracija servera je ažurirana", "MixedContent": "Miješani sadržaj", "Movies": "Filmovi", "Music": "Glazba", @@ -113,5 +113,7 @@ "TaskCleanLogsDescription": "Briši logove koji su stariji od {0} dana.", "TaskCleanLogs": "Očisti direktorij sa logovima", "TasksChannelsCategory": "Internet kanali", - "TasksLibraryCategory": "Biblioteka" + "TasksLibraryCategory": "Biblioteka", + "TaskCleanActivityLogDescription": "Briše zapise dnevnika aktivnosti starije od navedenog vremena.", + "TaskCleanActivityLog": "Očisti dnevnik aktivnosti" } From 826f58b461fb892ff82b1902168b632f41ba5020 Mon Sep 17 00:00:00 2001 From: Tomislav Date: Sat, 7 Nov 2020 21:30:03 +0000 Subject: [PATCH 171/203] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/ --- .../Localization/Core/hr.json | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index 712ffde699..4f570c8126 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -42,26 +42,26 @@ "MusicVideos": "Glazbeni spotovi", "NameInstallFailed": "{0} neuspješnih instalacija", "NameSeasonNumber": "Sezona {0}", - "NameSeasonUnknown": "Nepoznata sezona", + "NameSeasonUnknown": "Sezona nepoznata", "NewVersionIsAvailable": "Nova verzija Jellyfin servera je dostupna za preuzimanje.", - "NotificationOptionApplicationUpdateAvailable": "Dostupno ažuriranje aplikacije", - "NotificationOptionApplicationUpdateInstalled": "Instalirano ažuriranje aplikacije", - "NotificationOptionAudioPlayback": "Reprodukcija glazbe započeta", - "NotificationOptionAudioPlaybackStopped": "Reprodukcija audiozapisa je zaustavljena", - "NotificationOptionCameraImageUploaded": "Slike kamere preuzete", - "NotificationOptionInstallationFailed": "Instalacija neuspješna", - "NotificationOptionNewLibraryContent": "Novi sadržaj je dodan", - "NotificationOptionPluginError": "Dodatak otkazao", + "NotificationOptionApplicationUpdateAvailable": "Dostupno je ažuriranje aplikacije", + "NotificationOptionApplicationUpdateInstalled": "Instalirano je ažuriranje aplikacije", + "NotificationOptionAudioPlayback": "Reprodukcija glazbe započela", + "NotificationOptionAudioPlaybackStopped": "Reprodukcija glazbe zaustavljena", + "NotificationOptionCameraImageUploaded": "Slika s kamere učitana", + "NotificationOptionInstallationFailed": "Instalacija nije uspjela", + "NotificationOptionNewLibraryContent": "Novi sadržaj dodan", + "NotificationOptionPluginError": "Dodatak zakazao", "NotificationOptionPluginInstalled": "Dodatak instaliran", - "NotificationOptionPluginUninstalled": "Dodatak uklonjen", - "NotificationOptionPluginUpdateInstalled": "Instalirano ažuriranje za dodatak", - "NotificationOptionServerRestartRequired": "Potrebno ponovo pokretanje servera", - "NotificationOptionTaskFailed": "Zakazan zadatak nije izvršen", + "NotificationOptionPluginUninstalled": "Dodatak deinstaliran", + "NotificationOptionPluginUpdateInstalled": "Instalirano ažuriranje dodatka", + "NotificationOptionServerRestartRequired": "Ponovno pokrenite server", + "NotificationOptionTaskFailed": "Greška zakazanog zadatka", "NotificationOptionUserLockedOut": "Korisnik zaključan", - "NotificationOptionVideoPlayback": "Reprodukcija videa započeta", - "NotificationOptionVideoPlaybackStopped": "Reprodukcija videozapisa je zaustavljena", - "Photos": "Slike", - "Playlists": "Popis za reprodukciju", + "NotificationOptionVideoPlayback": "Reprodukcija videa započela", + "NotificationOptionVideoPlaybackStopped": "Reprodukcija videa zaustavljena", + "Photos": "Fotografije", + "Playlists": "Popisi za reprodukciju", "Plugin": "Dodatak", "PluginInstalledWithName": "{0} je instalirano", "PluginUninstalledWithName": "{0} je deinstalirano", From 2afaa1fc5bbf63558be3b98cb9129b003a19681f Mon Sep 17 00:00:00 2001 From: Tomislav Date: Sat, 7 Nov 2020 21:43:57 +0000 Subject: [PATCH 172/203] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/ --- .../Localization/Core/hr.json | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index 4f570c8126..15f24d8c99 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -66,38 +66,38 @@ "PluginInstalledWithName": "{0} je instalirano", "PluginUninstalledWithName": "{0} je deinstalirano", "PluginUpdatedWithName": "{0} je ažurirano", - "ProviderValue": "Pružitelj: {0}", + "ProviderValue": "Pružatelj: {0}", "ScheduledTaskFailedWithName": "{0} neuspjelo", "ScheduledTaskStartedWithName": "{0} pokrenuto", - "ServerNameNeedsToBeRestarted": "{0} treba biti ponovno pokrenuto", + "ServerNameNeedsToBeRestarted": "{0} treba ponovno pokrenuti", "Shows": "Serije", "Songs": "Pjesme", - "StartupEmbyServerIsLoading": "Jellyfin Server se učitava. Pokušajte ponovo kasnije.", + "StartupEmbyServerIsLoading": "Jellyfin server se učitava. Pokušajte ponovo uskoro.", "SubtitleDownloadFailureForItem": "Titlovi prijevoda nisu preuzeti za {0}", - "SubtitleDownloadFailureFromForItem": "Prijevodi nisu uspješno preuzeti {0} od {1}", - "Sync": "Sink.", - "System": "Sistem", + "SubtitleDownloadFailureFromForItem": "Prijevod nije uspješno preuzet od {0} za {1}", + "Sync": "Sinkronizacija", + "System": "Sustav", "TvShows": "Serije", "User": "Korisnik", - "UserCreatedWithName": "Korisnik {0} je stvoren", + "UserCreatedWithName": "Korisnik {0} je kreiran", "UserDeletedWithName": "Korisnik {0} je obrisan", - "UserDownloadingItemWithValues": "{0} se preuzima {1}", + "UserDownloadingItemWithValues": "{0} preuzima {1}", "UserLockedOutWithName": "Korisnik {0} je zaključan", - "UserOfflineFromDevice": "{0} se odspojilo od {1}", - "UserOnlineFromDevice": "{0} je online od {1}", + "UserOfflineFromDevice": "{0} prekinuo vezu od {1}", + "UserOnlineFromDevice": "{0} povezan od {1}", "UserPasswordChangedWithName": "Lozinka je promijenjena za korisnika {0}", - "UserPolicyUpdatedWithName": "Pravila za korisnika su ažurirana za {0}", - "UserStartedPlayingItemWithValues": "{0} je pokrenuo {1}", - "UserStoppedPlayingItemWithValues": "{0} je zaustavio {1}", + "UserPolicyUpdatedWithName": "Pravila za korisnika ažurirana su za {0}", + "UserStartedPlayingItemWithValues": "{0} je pokrenuo reprodukciju {1} na {2}", + "UserStoppedPlayingItemWithValues": "{0} je završio reprodukciju {1} na {2}", "ValueHasBeenAddedToLibrary": "{0} je dodano u medijsku biblioteku", - "ValueSpecialEpisodeName": "Specijal - {0}", + "ValueSpecialEpisodeName": "Posebno - {0}", "VersionNumber": "Verzija {0}", "TaskRefreshLibraryDescription": "Skenira vašu medijsku knjižnicu sa novim datotekama i osvježuje metapodatke.", "TaskRefreshLibrary": "Skeniraj medijsku knjižnicu", "TaskRefreshChapterImagesDescription": "Stvara sličice za videozapise koji imaju poglavlja.", "TaskRefreshChapterImages": "Raspakiraj slike poglavlja", "TaskCleanCacheDescription": "Briše priručne datoteke nepotrebne za sistem.", - "TaskCleanCache": "Očisti priručnu memoriju", + "TaskCleanCache": "Očisti mapu predmemorije", "TasksApplicationCategory": "Aplikacija", "TasksMaintenanceCategory": "Održavanje", "TaskDownloadMissingSubtitlesDescription": "Pretraživanje interneta za prijevodima koji nedostaju bazirano na konfiguraciji meta podataka.", From a7f2073c68dae639ccc555a0c57ea0f8392e33a3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 7 Nov 2020 17:30:56 -0700 Subject: [PATCH 173/203] Add missing dlna attributes. --- .../Controllers/DlnaServerController.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index 271ae293b4..4e6455eaa7 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -77,6 +77,7 @@ namespace Jellyfin.Api.Controllers /// Gets Dlna media receiver registrar xml. /// /// Server UUID. + /// Dlna media receiver registrar xml returned. /// Dlna media receiver registrar xml. [HttpGet("{serverId}/MediaReceiverRegistrar")] [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")] @@ -94,6 +95,7 @@ namespace Jellyfin.Api.Controllers /// Gets Dlna media receiver registrar xml. /// /// Server UUID. + /// Dlna media receiver registrar xml returned. /// Dlna media receiver registrar xml. [HttpGet("{serverId}/ConnectionManager")] [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")] @@ -111,8 +113,12 @@ namespace Jellyfin.Api.Controllers /// Process a content directory control request. /// /// Server UUID. + /// Request processed. /// Control response. [HttpPost("{serverId}/ContentDirectory/Control")] + [ProducesResponseType(StatusCodes.Status200OK)] + [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); @@ -122,8 +128,12 @@ namespace Jellyfin.Api.Controllers /// Process a connection manager control request. /// /// Server UUID. + /// Request processed. /// Control response. [HttpPost("{serverId}/ConnectionManager/Control")] + [ProducesResponseType(StatusCodes.Status200OK)] + [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); @@ -133,8 +143,12 @@ namespace Jellyfin.Api.Controllers /// Process a media receiver registrar control request. /// /// Server UUID. + /// Request processed. /// Control response. [HttpPost("{serverId}/MediaReceiverRegistrar/Control")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(MediaTypeNames.Text.Xml)] + [ProducesFile(MediaTypeNames.Text.Xml)] public async Task> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId) { return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); @@ -144,11 +158,15 @@ namespace Jellyfin.Api.Controllers /// Processes an event subscription request. /// /// Server UUID. + /// Request processed. /// 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)] + [Produces(MediaTypeNames.Text.Xml)] + [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessMediaReceiverRegistrarEventRequest(string serverId) { return ProcessEventRequest(_mediaReceiverRegistrar); @@ -158,11 +176,15 @@ namespace Jellyfin.Api.Controllers /// Processes an event subscription request. /// /// Server UUID. + /// Request processed. /// 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)] + [Produces(MediaTypeNames.Text.Xml)] + [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessContentDirectoryEventRequest(string serverId) { return ProcessEventRequest(_contentDirectory); @@ -172,11 +194,15 @@ namespace Jellyfin.Api.Controllers /// Processes an event subscription request. /// /// Server UUID. + /// Request processed. /// 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)] + [Produces(MediaTypeNames.Text.Xml)] + [ProducesFile(MediaTypeNames.Text.Xml)] public ActionResult ProcessConnectionManagerEventRequest(string serverId) { return ProcessEventRequest(_connectionManager); From dc3f24c11201b320206ef11ca5dab97358445048 Mon Sep 17 00:00:00 2001 From: Winnie Date: Sun, 8 Nov 2020 00:19:54 +0000 Subject: [PATCH 174/203] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/ --- Emby.Server.Implementations/Localization/Core/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index d4e0b299dd..60abc08d40 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -77,7 +77,7 @@ "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}", "Sync": "Sincronizar", "System": "Sistema", - "TvShows": "Programas de televisión", + "TvShows": "Series", "User": "Usuario", "UserCreatedWithName": "El usuario {0} ha sido creado", "UserDeletedWithName": "El usuario {0} ha sido borrado", From ad1b08f2d63bc8aec79f8bbd131c1c85e65a0eba Mon Sep 17 00:00:00 2001 From: Tomislav Date: Sat, 7 Nov 2020 22:00:12 +0000 Subject: [PATCH 175/203] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/ --- .../Localization/Core/hr.json | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index 15f24d8c99..9be91b7240 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -92,26 +92,26 @@ "ValueHasBeenAddedToLibrary": "{0} je dodano u medijsku biblioteku", "ValueSpecialEpisodeName": "Posebno - {0}", "VersionNumber": "Verzija {0}", - "TaskRefreshLibraryDescription": "Skenira vašu medijsku knjižnicu sa novim datotekama i osvježuje metapodatke.", - "TaskRefreshLibrary": "Skeniraj medijsku knjižnicu", - "TaskRefreshChapterImagesDescription": "Stvara sličice za videozapise koji imaju poglavlja.", - "TaskRefreshChapterImages": "Raspakiraj slike poglavlja", - "TaskCleanCacheDescription": "Briše priručne datoteke nepotrebne za sistem.", + "TaskRefreshLibraryDescription": "Skenira medijsku biblioteku radi novih datoteka i osvježava metapodatke.", + "TaskRefreshLibrary": "Skeniraj medijsku biblioteku", + "TaskRefreshChapterImagesDescription": "Kreira sličice za videozapise koji imaju poglavlja.", + "TaskRefreshChapterImages": "Izdvoji slike poglavlja", + "TaskCleanCacheDescription": "Briše nepotrebne datoteke iz predmemorije.", "TaskCleanCache": "Očisti mapu predmemorije", "TasksApplicationCategory": "Aplikacija", "TasksMaintenanceCategory": "Održavanje", - "TaskDownloadMissingSubtitlesDescription": "Pretraživanje interneta za prijevodima koji nedostaju bazirano na konfiguraciji meta podataka.", - "TaskDownloadMissingSubtitles": "Preuzimanje prijevoda koji nedostaju", - "TaskRefreshChannelsDescription": "Osvježava informacije o internet kanalima.", + "TaskDownloadMissingSubtitlesDescription": "Pretraži Internet za prijevodima koji nedostaju prema konfiguraciji metapodataka.", + "TaskDownloadMissingSubtitles": "Preuzmi prijevod koji nedostaje", + "TaskRefreshChannelsDescription": "Osvježava informacije Internet kanala.", "TaskRefreshChannels": "Osvježi kanale", - "TaskCleanTranscodeDescription": "Briše transkodirane fajlove starije od jednog dana.", - "TaskCleanTranscode": "Očisti direktorij za transkodiranje", - "TaskUpdatePluginsDescription": "Preuzima i instalira ažuriranja za dodatke koji su podešeni da se ažuriraju automatski.", + "TaskCleanTranscodeDescription": "Briše transkodirane datoteke starije od jednog dana.", + "TaskCleanTranscode": "Očisti mapu transkodiranja", + "TaskUpdatePluginsDescription": "Preuzima i instalira ažuriranja za dodatke koji su konfigurirani da se ažuriraju automatski.", "TaskUpdatePlugins": "Ažuriraj dodatke", - "TaskRefreshPeopleDescription": "Ažurira meta podatke za glumce i redatelje u vašoj medijskoj biblioteci.", - "TaskRefreshPeople": "Osvježi ljude", - "TaskCleanLogsDescription": "Briši logove koji su stariji od {0} dana.", - "TaskCleanLogs": "Očisti direktorij sa logovima", + "TaskRefreshPeopleDescription": "Ažurira metapodatke za glumce i redatelje u medijskoj biblioteci.", + "TaskRefreshPeople": "Osvježi osobe", + "TaskCleanLogsDescription": "Briše zapise dnevnika koji su stariji od {0} dana.", + "TaskCleanLogs": "Očisti mapu dnevnika zapisa", "TasksChannelsCategory": "Internet kanali", "TasksLibraryCategory": "Biblioteka", "TaskCleanActivityLogDescription": "Briše zapise dnevnika aktivnosti starije od navedenog vremena.", From 1bd5f780250993b01e551699f54f703032a0d1dd Mon Sep 17 00:00:00 2001 From: johan456789 Date: Sat, 7 Nov 2020 22:20:54 +0000 Subject: [PATCH 176/203] Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant/ --- Emby.Server.Implementations/Localization/Core/zh-TW.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index 30f7266300..d2e3d77a3a 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -112,5 +112,7 @@ "TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。", "TasksChannelsCategory": "網路頻道", "TasksApplicationCategory": "應用程式", - "TasksMaintenanceCategory": "維修" + "TasksMaintenanceCategory": "維護", + "TaskCleanActivityLogDescription": "刪除超過所設時間的活動紀錄。", + "TaskCleanActivityLog": "清除活動紀錄" } From 0d1d0d113e9a8b272201f0455df5d51014a6d430 Mon Sep 17 00:00:00 2001 From: Ricky Zhang Date: Sun, 8 Nov 2020 06:07:42 +0000 Subject: [PATCH 177/203] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 53a902de28..e98047a369 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -113,5 +113,6 @@ "TaskCleanCacheDescription": "删除系统不再需要的缓存文件。", "TaskCleanCache": "清理缓存目录", "TasksApplicationCategory": "应用程序", - "TasksMaintenanceCategory": "维护" + "TasksMaintenanceCategory": "维护", + "TaskCleanActivityLog": "清理程序日志" } From 38185f832688a3329045ee1bb26a38445c7777b2 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 8 Nov 2020 11:20:28 +0100 Subject: [PATCH 178/203] Apply suggestions from code review --- MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index f4a58775db..49ee70bb55 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -154,7 +154,7 @@ namespace MediaBrowser.Providers.Subtitles { var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false); - await TrySaveSubtitle(video, libraryOptions, response); + await TrySaveSubtitle(video, libraryOptions, response).ConfigureAwait(false); } catch (RateLimitExceededException) { @@ -174,10 +174,10 @@ namespace MediaBrowser.Providers.Subtitles } /// - public async Task UploadSubtitle(Video video, SubtitleResponse response) + public Task UploadSubtitle(Video video, SubtitleResponse response) { var libraryOptions = BaseItem.LibraryManager.GetLibraryOptions(video); - await TrySaveSubtitle(video, libraryOptions, response).ConfigureAwait(false); + return TrySaveSubtitle(video, libraryOptions, response); } private async Task TrySaveSubtitle( From 2229ca38aca0d3007473a52c9560cec0bac8ac05 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 8 Nov 2020 19:33:40 +0800 Subject: [PATCH 179/203] pascal case --- Jellyfin.Api/Controllers/SubtitleController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 4c9a153840..a01ae31a0e 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -459,7 +459,7 @@ namespace Jellyfin.Api.Controllers if (fontFile != null && fileSize != null && fileSize > 0) { - _logger.LogDebug("Fallback font size is {FileSize} Bytes", fileSize); + _logger.LogDebug("Fallback font size is {fileSize} Bytes", fileSize); return PhysicalFile(fontFile.FullName, MimeTypes.GetMimeType(fontFile.FullName)); } else From 866c41519f2dcf40bb881cec4d8995e17a99b596 Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Sun, 8 Nov 2020 12:34:35 +0000 Subject: [PATCH 180/203] Perform hashing of Password for Schedules Direct on server --- .../LiveTv/Listings/SchedulesDirect.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 28aabc1599..8735745c6b 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -15,6 +15,7 @@ using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -33,17 +34,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly IHttpClientFactory _httpClientFactory; private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); private readonly IApplicationHost _appHost; + private readonly ICryptoProvider _cryptoProvider; public SchedulesDirect( ILogger logger, IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, - IApplicationHost appHost) + IApplicationHost appHost, + ICryptoProvider cryptoProvider) { _logger = logger; _jsonSerializer = jsonSerializer; _httpClientFactory = httpClientFactory; _appHost = appHost; + _cryptoProvider = cryptoProvider; } private string UserAgent => _appHost.ApplicationUserAgent; @@ -642,7 +646,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken cancellationToken) { using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token"); - options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json); + var hashedPasswordBytes = _cryptoProvider.ComputeHash("SHA1", Encoding.ASCII.GetBytes(password), Array.Empty()); + string hashedPassword = string.Concat(hashedPasswordBytes.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + hashedPassword + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); From e78c63c4dc819867acddc5a15a7d7c02f7aa9b30 Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 8 Nov 2020 16:10:33 +0100 Subject: [PATCH 181/203] Remove OriginalAuthenticationInfo and add IsAuthenticated property --- .../HttpServer/Security/AuthService.cs | 5 ++-- .../Security/AuthorizationContext.cs | 25 +++++++++---------- .../Auth/CustomAuthenticationHandler.cs | 2 +- .../Net/AuthorizationInfo.cs | 5 ++++ .../Auth/CustomAuthenticationHandlerTests.cs | 5 ++-- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 7d53e886f7..df7a034e8f 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; @@ -19,9 +20,9 @@ namespace Emby.Server.Implementations.HttpServer.Security public AuthorizationInfo Authenticate(HttpRequest request) { var auth = _authorizationContext.GetAuthorizationInfo(request); - if (auth == null) + if (!auth.IsAuthenticated) { - throw new SecurityException("Unauthenticated request."); + throw new AuthenticationException("Invalid token."); } if (auth.User?.HasPermission(PermissionKind.IsDisabled) ?? false) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index de7e7bf3b8..e733c9092a 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -36,8 +36,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext) { var auth = GetAuthorizationDictionary(requestContext); - var (authInfo, _) = - GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query); + var authInfo = GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query); return authInfo; } @@ -49,19 +48,13 @@ namespace Emby.Server.Implementations.HttpServer.Security private AuthorizationInfo GetAuthorization(HttpContext httpReq) { var auth = GetAuthorizationDictionary(httpReq); - var (authInfo, originalAuthInfo) = - GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query); - - if (originalAuthInfo != null) - { - httpReq.Request.HttpContext.Items["OriginalAuthenticationInfo"] = originalAuthInfo; - } + var authInfo = GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query); httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo; return authInfo; } - private (AuthorizationInfo authInfo, AuthenticationInfo originalAuthenticationInfo) GetAuthorizationInfoFromDictionary( + private AuthorizationInfo GetAuthorizationInfoFromDictionary( in Dictionary auth, in IHeaderDictionary headers, in IQueryCollection queryString) @@ -108,13 +101,14 @@ namespace Emby.Server.Implementations.HttpServer.Security Device = device, DeviceId = deviceId, Version = version, - Token = token + Token = token, + IsAuthenticated = false }; if (string.IsNullOrWhiteSpace(token)) { // Request doesn't contain a token. - return (null, null); + return authInfo; } var result = _authRepo.Get(new AuthenticationInfoQuery @@ -122,6 +116,11 @@ namespace Emby.Server.Implementations.HttpServer.Security AccessToken = token }); + if (result.Items.Count > 0) + { + authInfo.IsAuthenticated = true; + } + var originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null; if (originalAuthenticationInfo != null) @@ -197,7 +196,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - return (authInfo, originalAuthenticationInfo); + return authInfo; } /// diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index e8cc389072..27a1f61be0 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -1,10 +1,10 @@ using System.Globalization; -using System.Security.Authentication; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index 5c642edff2..0194c596f1 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -53,5 +53,10 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the user making the request. /// public User User { get; set; } + + /// + /// Gets or sets a value indicating whether the token is authenticated. + /// + public bool IsAuthenticated { get; set; } } } diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs index 33534abd2f..a46d94457f 100644 --- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Auth; using Jellyfin.Api.Constants; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; @@ -68,14 +69,14 @@ namespace Jellyfin.Api.Tests.Auth } [Fact] - public async Task HandleAuthenticateAsyncShouldFailOnSecurityException() + public async Task HandleAuthenticateAsyncShouldFailOnAuthenticationException() { var errorMessage = _fixture.Create(); _jellyfinAuthServiceMock.Setup( a => a.Authenticate( It.IsAny())) - .Throws(new SecurityException(errorMessage)); + .Throws(new AuthenticationException(errorMessage)); var authenticateResult = await _sut.AuthenticateAsync(); From 73f923c8d50cdf673fe094c33f6e92b77144bedf Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 8 Nov 2020 09:31:53 -0700 Subject: [PATCH 182/203] Use class instead of struct --- MediaBrowser.Common/Plugins/LocalPlugin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index 922fad23ae..7927c663d4 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -7,10 +7,10 @@ namespace MediaBrowser.Common.Plugins /// /// Local plugin struct. /// - public readonly struct LocalPlugin : IEquatable + public class LocalPlugin : IEquatable { /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the class. /// /// The plugin id. /// The plugin name. From e9d35cb2cab134c1f285d695778424fafd8b35d6 Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Sun, 8 Nov 2020 17:16:51 +0000 Subject: [PATCH 183/203] Switching to the more efficient Hex.Encode function --- Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 8735745c6b..aacadde504 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -647,7 +647,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token"); var hashedPasswordBytes = _cryptoProvider.ComputeHash("SHA1", Encoding.ASCII.GetBytes(password), Array.Empty()); - string hashedPassword = string.Concat(hashedPasswordBytes.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + string hashedPassword = Hex.Encode(hashedPasswordBytes); options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + hashedPassword + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false); From d2f439efd29301774a54cbec907fe16e237cc7c5 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 8 Nov 2020 10:22:38 -0700 Subject: [PATCH 184/203] Remove unstable npm ci task --- .ci/azure-pipelines-api-client.yml | 19 ------------------- .../templates/typescript/axios/generate.sh | 10 +--------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index de6bbf04ce..03102121ff 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -35,14 +35,6 @@ jobs: customEndpoint: 'jellyfin-bot for NPM' ## Generate npm api client -# Unstable - - task: CmdLine@2 - displayName: 'Build unstable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') - inputs: - script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory) $(Build.BuildNumber)" - -# Stable - task: CmdLine@2 displayName: 'Build stable typescript axios client' condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') @@ -57,17 +49,6 @@ jobs: workingDir: ./apiclient/generated/typescript/axios ## Publish npm packages -# Unstable - - task: Npm@1 - displayName: 'Publish unstable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') - inputs: - command: publish - publishRegistry: useFeed - publishFeed: 'jellyfin/unstable' - workingDir: ./apiclient/generated/typescript/axios - -# Stable - task: Npm@1 displayName: 'Publish stable typescript axios client' condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') diff --git a/apiclient/templates/typescript/axios/generate.sh b/apiclient/templates/typescript/axios/generate.sh index 8c4d742825..9599f85dbd 100644 --- a/apiclient/templates/typescript/axios/generate.sh +++ b/apiclient/templates/typescript/axios/generate.sh @@ -1,14 +1,6 @@ #!/bin/bash artifactsDirectory="${1}" -buildNumber="${2}" -if [[ -n ${buildNumber} ]]; then - # Unstable build - additionalProperties=",snapshotVersion=-SNAPSHOT.${buildNumber},npmRepository=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/" -else - # Stable build - additionalProperties="" -fi java -jar openapi-generator-cli.jar generate \ --input-spec ${artifactsDirectory}/openapispec/openapi.json \ @@ -16,4 +8,4 @@ java -jar openapi-generator-cli.jar generate \ --output ./apiclient/generated/typescript/axios \ --template-dir ./apiclient/templates/typescript/axios \ --ignore-file-override ./apiclient/.openapi-generator-ignore \ - --additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",modelPackage="models",apiPackage="api",npmName="axios"${additionalProperties} + --additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",modelPackage="models",apiPackage="api",npmName="axios" From e5c0aaead2f190e080e534fcd4ba36d874896104 Mon Sep 17 00:00:00 2001 From: Ekrem KANGAL Date: Sun, 8 Nov 2020 15:05:52 +0000 Subject: [PATCH 185/203] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/ --- Emby.Server.Implementations/Localization/Core/tr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 818b57c7f6..54d3a65f0d 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -114,5 +114,6 @@ "TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.", "TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar", "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.", - "TaskCleanActivityLog": "İşlem Günlüğünü Temizle" + "TaskCleanActivityLog": "İşlem Günlüğünü Temizle", + "TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi." } From 363d41f9435e5dbdc8e933690cff66f89d3808fc Mon Sep 17 00:00:00 2001 From: Kyle Yue Date: Sun, 8 Nov 2020 11:46:46 +0000 Subject: [PATCH 186/203] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index e98047a369..3ae0fe5e77 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -114,5 +114,6 @@ "TaskCleanCache": "清理缓存目录", "TasksApplicationCategory": "应用程序", "TasksMaintenanceCategory": "维护", - "TaskCleanActivityLog": "清理程序日志" + "TaskCleanActivityLog": "清理程序日志", + "TaskCleanActivityLogDescription": "删除早于设置时间的活动日志条目。" } From 254c188f6cf0e4c53df012d6c471924550ec4490 Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Mon, 9 Nov 2020 02:30:53 +0000 Subject: [PATCH 187/203] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index ac74deff82..ba58e4bebf 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -112,5 +112,7 @@ "Books": "Sách", "AuthenticationSucceededWithUserName": "{0} xác thực thành công", "Application": "Ứng Dụng", - "AppDeviceValues": "Ứng Dụng: {0}, Thiết Bị: {1}" + "AppDeviceValues": "Ứng Dụng: {0}, Thiết Bị: {1}", + "TaskCleanActivityLogDescription": "Xóa các mục nhật ký hoạt động cũ hơn độ tuổi đã cài đặt.", + "TaskCleanActivityLog": "Xóa Nhật Ký Hoạt Động" } From e15891df5109388254119e877c2d9e6e7114ffbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 12:00:39 +0000 Subject: [PATCH 188/203] Bump Microsoft.NET.Test.Sdk from 16.7.1 to 16.8.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.7.1 to 16.8.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.7.1...v16.8.0) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 2 +- .../Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj | 2 +- .../Jellyfin.MediaEncoding.Tests.csproj | 2 +- tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 0236f2ac1e..ce61f5684e 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index e3f87d29b7..67dc8286a3 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index 5de02a29ba..30e84842a0 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index 3ac60819b4..4fd0d53421 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 37d0a9929a..0d240fd65a 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 05323490e2..db1f2956ea 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -16,7 +16,7 @@ - + From 3874f570edca18e6ae3ae83c11418331acff2437 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 12:00:50 +0000 Subject: [PATCH 189/203] Bump Serilog.Sinks.Graylog from 2.2.1 to 2.2.2 Bumps [Serilog.Sinks.Graylog](https://github.com/whir1/serilog-sinks-graylog) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/whir1/serilog-sinks-graylog/releases) - [Commits](https://github.com/whir1/serilog-sinks-graylog/commits) Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index a64d2e1cdb..3558f144cf 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -50,7 +50,7 @@ - + From 78551d166a98eabe0f10c43b9fbb486a74887275 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 9 Nov 2020 06:10:16 -0700 Subject: [PATCH 190/203] Don't throw exception if name is null --- Emby.Naming/Video/CleanDateTimeParser.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs index 579c9e91e1..f05d540f8b 100644 --- a/Emby.Naming/Video/CleanDateTimeParser.cs +++ b/Emby.Naming/Video/CleanDateTimeParser.cs @@ -15,6 +15,11 @@ namespace Emby.Naming.Video public static CleanDateTimeResult Clean(string name, IReadOnlyList cleanDateTimeRegexes) { CleanDateTimeResult result = new CleanDateTimeResult(name); + if (string.IsNullOrEmpty(name)) + { + return result; + } + var len = cleanDateTimeRegexes.Count; for (int i = 0; i < len; i++) { From b99519898d2c9e8ba3020e11892718f1eca37c66 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 9 Nov 2020 20:15:15 +0100 Subject: [PATCH 191/203] Remove duplicate /Similar endpoints and add poor matching for artists and albums --- .../Data/SqliteItemRepository.cs | 28 ++- Jellyfin.Api/Controllers/AlbumsController.cs | 135 ------------- Jellyfin.Api/Controllers/LibraryController.cs | 158 ++++++--------- Jellyfin.Api/Helpers/SimilarItemsHelper.cs | 182 ------------------ 4 files changed, 87 insertions(+), 416 deletions(-) delete mode 100644 Jellyfin.Api/Controllers/AlbumsController.cs delete mode 100644 Jellyfin.Api/Helpers/SimilarItemsHelper.cs diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index acb75e9b89..0761b64bd2 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2403,11 +2403,11 @@ namespace Emby.Server.Implementations.Data if (string.IsNullOrEmpty(item.OfficialRating)) { - builder.Append("((OfficialRating is null) * 10)"); + builder.Append("(OfficialRating is null * 10)"); } else { - builder.Append("((OfficialRating=@ItemOfficialRating) * 10)"); + builder.Append("(OfficialRating=@ItemOfficialRating * 10)"); } if (item.ProductionYear.HasValue) @@ -2416,8 +2416,26 @@ namespace Emby.Server.Implementations.Data builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 5 Else 0 End )"); } - //// genres, tags - builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId)) * 10)"); + // genres, tags, studios, person, year? + builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId))"); + + if (item is MusicArtist) + { + // Match albums where the artist is AlbumArtist against other albums. + // It is assumed that similar albums => similar artists. + builder.Append( + @"+ (WITH artistValues AS ( + SELECT DISTINCT albumValues.CleanValue + FROM ItemValues albumValues + INNER JOIN ItemValues artistAlbums ON albumValues.ItemId = artistAlbums.ItemId + INNER JOIN TypedBaseItems artistItem ON artistAlbums.CleanValue = artistItem.CleanName AND artistAlbums.TYPE = 1 AND artistItem.Guid = @SimilarItemId + ), similarArtist AS ( + SELECT albumValues.ItemId + FROM ItemValues albumValues + INNER JOIN ItemValues artistAlbums ON albumValues.ItemId = artistAlbums.ItemId + INNER JOIN TypedBaseItems artistItem ON artistAlbums.CleanValue = artistItem.CleanName AND artistAlbums.TYPE = 1 AND artistItem.Guid = A.Guid + ) SELECT COUNT(DISTINCT(CleanValue)) * 10 FROM ItemValues WHERE ItemId IN (SELECT ItemId FROM similarArtist) AND CleanValue IN (SELECT CleanValue FROM artistValues))"); + } builder.Append(") as SimilarityScore"); @@ -5052,7 +5070,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); - var commandText = "select ItemId, Name, Role, PersonType, SortOrder from People"; + var commandText = "select ItemId, Name, Role, PersonType, SortOrder from People p"; var whereClauses = GetPeopleWhereClauses(query, null); diff --git a/Jellyfin.Api/Controllers/AlbumsController.cs b/Jellyfin.Api/Controllers/AlbumsController.cs deleted file mode 100644 index 357f646a2b..0000000000 --- a/Jellyfin.Api/Controllers/AlbumsController.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Jellyfin.Api.Controllers -{ - /// - /// The albums controller. - /// - [Route("")] - public class AlbumsController : BaseJellyfinApiController - { - private readonly IUserManager _userManager; - private readonly ILibraryManager _libraryManager; - private readonly IDtoService _dtoService; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - public AlbumsController( - IUserManager userManager, - ILibraryManager libraryManager, - IDtoService dtoService) - { - _userManager = userManager; - _libraryManager = libraryManager; - _dtoService = dtoService; - } - - /// - /// Finds albums similar to a given album. - /// - /// The album id. - /// Optional. Filter by user id, and attach user data. - /// Optional. Ids of artists to exclude. - /// Optional. The maximum number of records to return. - /// Similar albums returned. - /// A with similar albums. - [HttpGet("Albums/{albumId}/Similar")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetSimilarAlbums( - [FromRoute, Required] string albumId, - [FromQuery] Guid? userId, - [FromQuery] string? excludeArtistIds, - [FromQuery] int? limit) - { - var dtoOptions = new DtoOptions().AddClientFields(Request); - - return SimilarItemsHelper.GetSimilarItemsResult( - dtoOptions, - _userManager, - _libraryManager, - _dtoService, - userId, - albumId, - excludeArtistIds, - limit, - new[] { typeof(MusicAlbum) }, - GetAlbumSimilarityScore); - } - - /// - /// Finds artists similar to a given artist. - /// - /// The artist id. - /// Optional. Filter by user id, and attach user data. - /// Optional. Ids of artists to exclude. - /// Optional. The maximum number of records to return. - /// Similar artists returned. - /// A with similar artists. - [HttpGet("Artists/{artistId}/Similar")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetSimilarArtists( - [FromRoute, Required] string artistId, - [FromQuery] Guid? userId, - [FromQuery] string? excludeArtistIds, - [FromQuery] int? limit) - { - var dtoOptions = new DtoOptions().AddClientFields(Request); - - return SimilarItemsHelper.GetSimilarItemsResult( - dtoOptions, - _userManager, - _libraryManager, - _dtoService, - userId, - artistId, - excludeArtistIds, - limit, - new[] { typeof(MusicArtist) }, - SimilarItemsHelper.GetSimiliarityScore); - } - - /// - /// Gets a similairty score of two albums. - /// - /// The first item. - /// The item1 people. - /// All people. - /// The second item. - /// System.Int32. - private int GetAlbumSimilarityScore(BaseItem item1, List item1People, List allPeople, BaseItem item2) - { - var points = SimilarItemsHelper.GetSimiliarityScore(item1, item1People, allPeople, item2); - - var album1 = (MusicAlbum)item1; - var album2 = (MusicAlbum)item2; - - var artists1 = album1 - .GetAllArtists() - .DistinctNames() - .ToList(); - - var artists2 = new HashSet( - album2.GetAllArtists().DistinctNames(), - StringComparer.OrdinalIgnoreCase); - - return points + artists1.Where(artists2.Contains).Sum(i => 5); - } - } -} diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 8a872ae133..112415ef03 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -680,12 +680,12 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. /// Similar items returned. /// A containing the similar items. - [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists2")] + [HttpGet("Artists/{itemId}/Similar")] [HttpGet("Items/{itemId}/Similar")] - [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")] - [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")] - [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")] - [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")] + [HttpGet("Albums/{itemId}/Similar")] + [HttpGet("Shows/{itemId}/Similar")] + [HttpGet("Movies/{itemId}/Similar")] + [HttpGet("Trailers/{itemId}/Similar")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetSimilarItems( @@ -701,33 +701,72 @@ namespace Jellyfin.Api.Controllers : _libraryManager.RootFolder) : _libraryManager.GetItemById(itemId); + if (item is Episode || (item is IItemByName && !(item is MusicArtist))) + { + return new QueryResult(); + } + + var user = userId.HasValue && !userId.Equals(Guid.Empty) + ? _userManager.GetUserById(userId.Value) + : null; + var dtoOptions = new DtoOptions() + .AddItemFields(fields) + .AddClientFields(Request); + var program = item as IHasProgramAttributes; - var isMovie = item is MediaBrowser.Controller.Entities.Movies.Movie || (program != null && program.IsMovie) || item is Trailer; - if (program != null && program.IsSeries) + bool? isMovie = item is Movie || (program != null && program.IsMovie) || item is Trailer; + bool? isSeries = item is Series || (program != null && program.IsSeries); + + var includeItemTypes = new List(); + if (isMovie.Value) { - return GetSimilarItemsResult( - item, - excludeArtistIds, - userId, - limit, - fields, - new[] { nameof(Series) }, - false); + includeItemTypes.Add(nameof(Movie)); + if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) + { + includeItemTypes.Add(nameof(Trailer)); + includeItemTypes.Add(nameof(LiveTvProgram)); + } + } + else if (isSeries.Value) + { + includeItemTypes.Add(nameof(Series)); + } + else + { + // For non series and movie types these columns are typically null + isSeries = null; + isMovie = null; + includeItemTypes.Add(item.GetType().Name); } - if (item is MediaBrowser.Controller.Entities.TV.Episode || (item is IItemByName && !(item is MusicArtist))) + var query = new InternalItemsQuery(user) { - return new QueryResult(); + Limit = limit, + IncludeItemTypes = includeItemTypes.ToArray(), + IsMovie = isMovie, + IsSeries = isSeries, + SimilarTo = item, + DtoOptions = dtoOptions, + EnableTotalRecordCount = !isMovie ?? true, + EnableGroupByMetadataKey = isMovie ?? false, + MinSimilarityScore = 2 // A remnant from album/artist scoring + }; + + // ExcludeArtistIds + if (!string.IsNullOrEmpty(excludeArtistIds)) + { + query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds); } - return GetSimilarItemsResult( - item, - excludeArtistIds, - userId, - limit, - fields, - new[] { item.GetType().Name }, - isMovie); + List itemsResult = _libraryManager.GetItemList(query); + + var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); + + return new QueryResult + { + Items = returnList, + TotalRecordCount = itemsResult.Count + }; } /// @@ -880,75 +919,6 @@ namespace Jellyfin.Api.Controllers } } - private QueryResult GetSimilarItemsResult( - BaseItem item, - string? excludeArtistIds, - Guid? userId, - int? limit, - string? fields, - string[] includeItemTypes, - bool isMovie) - { - var user = userId.HasValue && !userId.Equals(Guid.Empty) - ? _userManager.GetUserById(userId.Value) - : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) - .AddClientFields(Request); - - var query = new InternalItemsQuery(user) - { - Limit = limit, - IncludeItemTypes = includeItemTypes, - IsMovie = isMovie, - SimilarTo = item, - DtoOptions = dtoOptions, - EnableTotalRecordCount = !isMovie, - EnableGroupByMetadataKey = isMovie - }; - - // ExcludeArtistIds - if (!string.IsNullOrEmpty(excludeArtistIds)) - { - query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds); - } - - List itemsResult; - - if (isMovie) - { - var itemTypes = new List { nameof(MediaBrowser.Controller.Entities.Movies.Movie) }; - if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) - { - itemTypes.Add(nameof(Trailer)); - itemTypes.Add(nameof(LiveTvProgram)); - } - - query.IncludeItemTypes = itemTypes.ToArray(); - itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList(); - } - else if (item is MusicArtist) - { - query.IncludeItemTypes = Array.Empty(); - - itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList(); - } - else - { - itemsResult = _libraryManager.GetItemList(query); - } - - var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); - - var result = new QueryResult - { - Items = returnList, - TotalRecordCount = itemsResult.Count - }; - - return result; - } - private static string[] GetRepresentativeItemTypes(string? contentType) { return contentType switch diff --git a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs deleted file mode 100644 index 6b06f87cd7..0000000000 --- a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; - -namespace Jellyfin.Api.Helpers -{ - /// - /// The similar items helper class. - /// - public static class SimilarItemsHelper - { - internal static QueryResult GetSimilarItemsResult( - DtoOptions dtoOptions, - IUserManager userManager, - ILibraryManager libraryManager, - IDtoService dtoService, - Guid? userId, - string id, - string? excludeArtistIds, - int? limit, - Type[] includeTypes, - Func, List, BaseItem, int> getSimilarityScore) - { - var user = userId.HasValue && !userId.Equals(Guid.Empty) - ? userManager.GetUserById(userId.Value) - : null; - - var item = string.IsNullOrEmpty(id) ? - (!userId.Equals(Guid.Empty) ? libraryManager.GetUserRootFolder() : - libraryManager.RootFolder) : libraryManager.GetItemById(id); - - var query = new InternalItemsQuery(user) - { - IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(), - Recursive = true, - DtoOptions = dtoOptions, - ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds) - }; - - var inputItems = libraryManager.GetItemList(query); - - var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore) - .ToList(); - - var returnItems = items; - - if (limit.HasValue && limit < returnItems.Count) - { - returnItems = returnItems.GetRange(0, limit.Value); - } - - var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); - - return new QueryResult - { - Items = dtos, - TotalRecordCount = items.Count - }; - } - - /// - /// Gets the similaritems. - /// - /// The item. - /// The library manager. - /// The input items. - /// The get similarity score. - /// IEnumerable{BaseItem}. - private static IEnumerable GetSimilaritems( - BaseItem item, - ILibraryManager libraryManager, - IEnumerable inputItems, - Func, List, BaseItem, int> getSimilarityScore) - { - var itemId = item.Id; - inputItems = inputItems.Where(i => i.Id != itemId); - var itemPeople = libraryManager.GetPeople(item); - var allPeople = libraryManager.GetPeople(new InternalPeopleQuery - { - AppearsInItemId = item.Id - }); - - return inputItems.Select(i => new Tuple(i, getSimilarityScore(item, itemPeople, allPeople, i))) - .Where(i => i.Item2 > 2) - .OrderByDescending(i => i.Item2) - .Select(i => i.Item1); - } - - private static IEnumerable GetTags(BaseItem item) - { - return item.Tags; - } - - /// - /// Gets the similiarity score. - /// - /// The item1. - /// The item1 people. - /// All people. - /// The item2. - /// System.Int32. - internal static int GetSimiliarityScore(BaseItem item1, List item1People, List allPeople, BaseItem item2) - { - var points = 0; - - if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase)) - { - points += 10; - } - - // Find common genres - points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10); - - // Find common tags - points += GetTags(item1).Where(i => GetTags(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10); - - // Find common studios - points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 3); - - var item2PeopleNames = allPeople.Where(i => i.ItemId == item2.Id) - .Select(i => i.Name) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .DistinctNames() - .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - - points += item1People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i => - { - if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase)) - { - return 5; - } - - if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - - if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - - if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - - if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase)) - { - return 2; - } - - return 1; - }); - - if (item1.ProductionYear.HasValue && item2.ProductionYear.HasValue) - { - var diff = Math.Abs(item1.ProductionYear.Value - item2.ProductionYear.Value); - - // Add if they came out within the same decade - if (diff < 10) - { - points += 2; - } - - // And more if within five years - if (diff < 5) - { - points += 2; - } - } - - return points; - } - } -} From b22831f7e551f11c0326a8b0977920f2250e0114 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 9 Nov 2020 14:53:23 -0700 Subject: [PATCH 192/203] Add ModelBinder to ImageType --- Jellyfin.Api/Controllers/ArtistsController.cs | 4 ++-- Jellyfin.Api/Controllers/GenresController.cs | 3 ++- Jellyfin.Api/Controllers/InstantMixController.cs | 15 ++++++++------- Jellyfin.Api/Controllers/ItemsController.cs | 6 +++--- Jellyfin.Api/Controllers/LiveTvController.cs | 11 ++++++----- Jellyfin.Api/Controllers/MusicGenresController.cs | 3 ++- Jellyfin.Api/Controllers/PersonsController.cs | 2 +- Jellyfin.Api/Controllers/PlaylistsController.cs | 3 ++- Jellyfin.Api/Controllers/StudiosController.cs | 3 ++- Jellyfin.Api/Controllers/TrailersController.cs | 4 ++-- Jellyfin.Api/Controllers/TvShowsController.cs | 9 +++++---- Jellyfin.Api/Controllers/UserLibraryController.cs | 3 ++- Jellyfin.Api/Controllers/YearsController.cs | 3 ++- 13 files changed, 39 insertions(+), 30 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 826fce6b0c..a35d7a556f 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, @@ -310,7 +310,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 4e47658b06..f6e0772ec1 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -4,6 +4,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 7f8a2be12e..2f342d0a8a 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -72,7 +73,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var item = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -109,7 +110,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var album = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -146,7 +147,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var playlist = (Playlist)_libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -183,7 +184,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) @@ -219,7 +220,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var item = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -256,7 +257,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var item = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -293,7 +294,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var item = _libraryManager.GetItemById(id); var user = userId.HasValue && !userId.Equals(Guid.Empty) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 7ca5775438..adc95f7574 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -186,7 +186,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, - [FromQuery] ImageType[] imageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery] string? sortBy, [FromQuery] bool? isPlayed, [FromQuery] string? genres, @@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, @@ -537,7 +537,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? mediaTypes, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] bool enableTotalRecordCount = true, diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 88a7542cee..31253cbbcd 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -14,6 +14,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LiveTvDtos; using Jellyfin.Data.Enums; using MediaBrowser.Common; @@ -146,7 +147,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isDisliked, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? fields, [FromQuery] bool? enableUserData, [FromQuery] string? sortBy, @@ -263,7 +264,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? seriesTimerId, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? fields, [FromQuery] bool? enableUserData, [FromQuery] bool? isMovie, @@ -350,7 +351,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? seriesTimerId, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? fields, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) @@ -561,7 +562,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? genreIds, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] string? seriesTimerId, [FromQuery] Guid? librarySeriesId, @@ -705,7 +706,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isSports, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? genreIds, [FromQuery] string? fields, [FromQuery] bool? enableUserData, diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index e105befe8e..e434f190ac 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -4,6 +4,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 1e0bdb6bc7..80395950cf 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -76,7 +76,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isFavorite, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? excludePersonTypes, [FromQuery] string? personTypes, [FromQuery] string? appearsInItemId, diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 0419b2436b..f2213f20d8 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.PlaylistDtos; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; @@ -152,7 +153,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var playlist = (Playlist)_libraryManager.GetItemById(playlistId); if (playlist == null) diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index c5fcfb3569..c60d14479d 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isFavorite, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 281c0016e9..6b4eee718b 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -151,7 +151,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] string? mediaTypes, - [FromQuery] ImageType[] imageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery] string? sortBy, [FromQuery] bool? isPlayed, [FromQuery] string? genres, @@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery] string? personIds, [FromQuery] string? personTypes, diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 49a6c386fe..55cdae39d0 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; @@ -78,7 +79,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? parentId, [FromQuery] bool? enableImges, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { @@ -135,7 +136,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? parentId, [FromQuery] bool? enableImges, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData) { var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -207,7 +208,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] string? sortBy) { @@ -326,7 +327,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? adjacentTo, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData) { var user = userId.HasValue && !userId.Equals(Guid.Empty) diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index a52af17815..faa8a0634b 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -272,7 +273,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isPlayed, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] int limit = 20, [FromQuery] bool groupItems = true) diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 2c685309a6..1712a6a543 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -5,6 +5,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -78,7 +79,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? sortBy, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] bool recursive = true, [FromQuery] bool? enableImages = true) From 0dd2b169a3d9b0c1b31cd83b4022c729e28f0d3b Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 9 Nov 2020 14:59:04 -0700 Subject: [PATCH 193/203] Add ModelBinder to ItemFilter --- Jellyfin.Api/Controllers/ArtistsController.cs | 4 ++-- Jellyfin.Api/Controllers/ChannelsController.cs | 4 ++-- Jellyfin.Api/Controllers/GenresController.cs | 3 ++- Jellyfin.Api/Controllers/InstantMixController.cs | 15 ++++++++------- Jellyfin.Api/Controllers/ItemsController.cs | 4 ++-- Jellyfin.Api/Controllers/LibraryController.cs | 3 ++- Jellyfin.Api/Controllers/LiveTvController.cs | 11 ++++++----- Jellyfin.Api/Controllers/MoviesController.cs | 3 ++- Jellyfin.Api/Controllers/MusicGenresController.cs | 3 ++- Jellyfin.Api/Controllers/PersonsController.cs | 2 +- Jellyfin.Api/Controllers/PlaylistsController.cs | 3 ++- Jellyfin.Api/Controllers/StudiosController.cs | 3 ++- Jellyfin.Api/Controllers/TrailersController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 9 +++++---- Jellyfin.Api/Controllers/UserLibraryController.cs | 3 ++- Jellyfin.Api/Controllers/YearsController.cs | 3 ++- 16 files changed, 43 insertions(+), 32 deletions(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 4394abf9ef..f58c136ef8 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, @@ -296,7 +296,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index b8df49a028..eed54105da 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -124,7 +124,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? sortOrder, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] string? sortBy, - [FromQuery] ItemFields[] fields) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) { var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) @@ -197,7 +197,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? channelIds) { var user = userId.HasValue && !userId.Equals(Guid.Empty) diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index a273c33dcb..468ad7b81b 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -4,6 +4,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -72,7 +73,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] bool? isFavorite, diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index a4c91d1d8f..7d68e9ab45 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -68,7 +69,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -104,7 +105,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -140,7 +141,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -176,7 +177,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -211,7 +212,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -247,7 +248,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -283,7 +284,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 2d98f91c80..3d939453e2 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -180,7 +180,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] string? sortOrder, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, @@ -532,7 +532,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? mediaTypes, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index e0f0bda26f..2eb8363d7c 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -12,6 +12,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LibraryDtos; using Jellyfin.Data.Entities; using MediaBrowser.Common.Progress; @@ -693,7 +694,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? excludeArtistIds, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) { var item = itemId.Equals(Guid.Empty) ? (!userId.Equals(Guid.Empty) diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index dc1dca1941..2edfffa483 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -14,6 +14,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LiveTvDtos; using Jellyfin.Data.Enums; using MediaBrowser.Common; @@ -147,7 +148,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, [FromQuery] ImageType[] enableImageTypes, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder, @@ -263,7 +264,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, [FromQuery] ImageType[] enableImageTypes, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool? isMovie, [FromQuery] bool? isSeries, @@ -349,7 +350,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, [FromQuery] ImageType[] enableImageTypes, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { @@ -563,7 +564,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableUserData, [FromQuery] string? seriesTimerId, [FromQuery] Guid? librarySeriesId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool enableTotalRecordCount = true) { var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -703,7 +704,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? imageTypeLimit, [FromQuery] ImageType[] enableImageTypes, [FromQuery] string? genreIds, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index b37c3f0b35..ebc148fe56 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; @@ -65,7 +66,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetMovieRecommendations( [FromQuery] Guid? userId, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] int categoryLimit = 5, [FromQuery] int itemLimit = 8) { diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 1af3bbccc9..7e122e7549 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -4,6 +4,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -72,7 +73,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] bool? isFavorite, diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index fcba75e67b..6c5b346cb7 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetPersons( [FromQuery] int? limit, [FromQuery] string? searchTerm, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] bool? enableUserData, diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index a1c8219ca9..07d3658cad 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.PlaylistDtos; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; @@ -148,7 +149,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, Required] Guid userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index bda1c0b2f3..99e6b7c8e5 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -71,7 +72,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] bool? isFavorite, diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 0806dc3e8a..9532df42fd 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -146,7 +146,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] string? sortOrder, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index e07e906001..901aeb43fb 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; @@ -73,7 +74,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? seriesId, [FromQuery] string? parentId, [FromQuery] bool? enableImges, @@ -130,7 +131,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? parentId, [FromQuery] bool? enableImges, [FromQuery] int? imageTypeLimit, @@ -195,7 +196,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetEpisodes( [FromRoute, Required] string seriesId, [FromQuery] Guid? userId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] int? season, [FromQuery] string? seasonId, [FromQuery] bool? isMissing, @@ -317,7 +318,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetSeasons( [FromRoute, Required] string seriesId, [FromQuery] Guid? userId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? isSpecialSeason, [FromQuery] bool? isMissing, [FromQuery] string? adjacentTo, diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index ff68cd497b..fd11adf9c5 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -267,7 +268,7 @@ namespace Jellyfin.Api.Controllers public ActionResult> GetLatestMedia( [FromRoute, Required] Guid userId, [FromQuery] Guid? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? includeItemTypes, [FromQuery] bool? isPlayed, [FromQuery] bool? enableImages, diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 0b4a2776b2..e146b69593 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -5,6 +5,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -71,7 +72,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] string? sortOrder, [FromQuery] string? parentId, - [FromQuery] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? excludeItemTypes, [FromQuery] string? includeItemTypes, [FromQuery] string? mediaTypes, From 2ce9a56cae36f2538f6a3df7d1c46f65e39750bc Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 9 Nov 2020 15:01:30 -0700 Subject: [PATCH 194/203] Remove GetImageTypes --- Jellyfin.Api/Helpers/RequestHelpers.cs | 27 -------------------------- 1 file changed, 27 deletions(-) diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 49632dd01a..13d6da3174 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -165,33 +165,6 @@ namespace Jellyfin.Api.Helpers .ToArray(); } - /// - /// Gets the item fields. - /// - /// The image types string. - /// IEnumerable{ItemFields}. - internal static ImageType[] GetImageTypes(string? imageTypes) - { - if (string.IsNullOrEmpty(imageTypes)) - { - return Array.Empty(); - } - - return Split(imageTypes, ',', true) - .Select(v => - { - if (Enum.TryParse(v, true, out ImageType value)) - { - return (ImageType?)value; - } - - return null; - }) - .Where(i => i.HasValue) - .Select(i => i!.Value) - .ToArray(); - } - internal static QueryResult CreateQueryResult( QueryResult<(BaseItem, ItemCounts)> result, DtoOptions dtoOptions, From 477e7f78985372297f3611f47c932da6448d27d9 Mon Sep 17 00:00:00 2001 From: Fardin Date: Mon, 9 Nov 2020 18:58:04 +0000 Subject: [PATCH 195/203] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/ --- Emby.Server.Implementations/Localization/Core/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 60abc08d40..d6af40c409 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -113,5 +113,7 @@ "TaskRefreshChannels": "Actualizar canales", "TaskRefreshChannelsDescription": "Actualiza la información de los canales de internet.", "TaskDownloadMissingSubtitles": "Descargar los subtítulos que faltan", - "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten en el contenido de tus bibliotecas, basándose en la configuración de los metadatos." + "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten en el contenido de tus bibliotecas, basándose en la configuración de los metadatos.", + "TaskCleanActivityLogDescription": "Elimina todos los registros de actividad anteriores a la fecha configurada.", + "TaskCleanActivityLog": "Limpiar registro de actividad" } From 103d503c1501436d855a774bc83ef656d68d587f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Mon, 9 Nov 2020 21:46:18 +0100 Subject: [PATCH 196/203] Removed code as suggested by @cvium --- .../Manager/MetadataService.cs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index a507bd4a6d..dca8acb7d8 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -252,7 +252,13 @@ namespace MediaBrowser.Providers.Manager if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) { - await AddPersonImageAsync(personEntity, libraryOptions, person.ImageUrl, cancellationToken).ConfigureAwait(false); + personEntity.SetImage( + new ItemImageInfo + { + Path = person.ImageUrl, + Type = ImageType.Primary + }, + 0); saveEntity = true; updateType |= ItemUpdateType.ImageUpdate; @@ -266,27 +272,6 @@ namespace MediaBrowser.Providers.Manager } } - private async Task AddPersonImageAsync(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken) - { - try - { - await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - return; - } - catch (Exception ex) - { - Logger.LogError(ex, "Error in AddPersonImage"); - } - - personEntity.SetImage( - new ItemImageInfo - { - Path = imageUrl, - Type = ImageType.Primary - }, - 0); - } - protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { item.AfterMetadataRefresh(); From d6a04fd406a4aa7d7fd40e3b8183c10c8d7e150f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Tue, 10 Nov 2020 00:20:12 +0100 Subject: [PATCH 197/203] Remove setting from existing libraries with a migration --- Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../Routines/RemoveDownloadImagesInAdvance.cs | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 844dae61f6..aca1654084 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -23,7 +23,8 @@ namespace Jellyfin.Server.Migrations typeof(Routines.AddDefaultPluginRepository), typeof(Routines.MigrateUserDb), typeof(Routines.ReaddDefaultPluginRepository), - typeof(Routines.MigrateDisplayPreferencesDb) + typeof(Routines.MigrateDisplayPreferencesDb), + typeof(Routines.RemoveDownloadImagesInAdvance) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs new file mode 100644 index 0000000000..b31deba844 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs @@ -0,0 +1,46 @@ +using System; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// + /// Removes the old 'RemoveDownloadImagesInAdvance' from library options. + /// + internal class RemoveDownloadImagesInAdvance : IMigrationRoutine + { + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + + public RemoveDownloadImagesInAdvance(ILogger logger, ILibraryManager libraryManager) + { + _logger = logger; + _libraryManager = libraryManager; + } + + /// + public Guid Id => Guid.Parse("{A81F75E0-8F43-416F-A5E8-516CCAB4D8CC}"); + + /// + public string Name => "RemoveDownloadImagesInAdvance"; + + /// + public bool PerformOnNewInstall => false; + + /// + public void Perform() + { + var virtual_folders = _libraryManager.GetVirtualFolders(false); + _logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries"); + foreach (var virtual_folder in virtual_folders) + { + var library_options = virtual_folder.LibraryOptions; + var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(virtual_folder.ItemId); + // The property no longer exists in LibraryOptions, so we just re-save the options to get old data removed. + collectionFolder.UpdateLibraryOptions(library_options); + _logger.LogInformation("Removed from '{VirtualFolder}'", virtual_folder.Name); + } + } + } +} From 27a1337cf3bea291a5f0be5fca9d8ee3470df525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Tue, 10 Nov 2020 10:24:05 +0100 Subject: [PATCH 198/203] Remove underscore Co-authored-by: Claus Vium --- .../Routines/RemoveDownloadImagesInAdvance.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs index b31deba844..42b87ec5f5 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs @@ -31,15 +31,15 @@ namespace Jellyfin.Server.Migrations.Routines /// public void Perform() { - var virtual_folders = _libraryManager.GetVirtualFolders(false); + var virtualFolders = _libraryManager.GetVirtualFolders(false); _logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries"); - foreach (var virtual_folder in virtual_folders) + foreach (var virtualFolder in virtualFolders) { - var library_options = virtual_folder.LibraryOptions; - var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(virtual_folder.ItemId); + var libraryOptions = virtualFolder.LibraryOptions; + var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(virtualFolder.ItemId); // The property no longer exists in LibraryOptions, so we just re-save the options to get old data removed. - collectionFolder.UpdateLibraryOptions(library_options); - _logger.LogInformation("Removed from '{VirtualFolder}'", virtual_folder.Name); + collectionFolder.UpdateLibraryOptions(libraryOptions); + _logger.LogInformation("Removed from '{VirtualFolder}'", virtualFolder.Name); } } } From f5b301dea66c6dd88ced57c0dbedf21b54145b5c Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 10 Nov 2020 11:48:21 -0700 Subject: [PATCH 199/203] Correct bad docs merge --- Jellyfin.Api/Controllers/ChannelsController.cs | 2 +- Jellyfin.Api/Controllers/PersonsController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index eed54105da..ec9d7cdce1 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -183,7 +183,7 @@ namespace Jellyfin.Api.Controllers /// Optional. User Id. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional. Specify additional fields of information to return in the output. /// Optional. Specify one or more channel id's, comma delimited. /// Latest channel items returned. diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index ac02795081..6ac3e6417a 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -54,7 +54,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// The search term. /// Optional. Specify additional fields of information to return in the output. - /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional filters to apply. /// Optional filter by items that are marked as favorite, or not. userId is required. /// Optional, include user data. /// Optional, the max number of images to return, per image type. From dc3b3bcfb1881743c078d13a100dc8f751ce6dff Mon Sep 17 00:00:00 2001 From: David John Corpuz Date: Tue, 10 Nov 2020 17:59:54 +0000 Subject: [PATCH 200/203] Translated using Weblate (Filipino) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fil/ --- .../Localization/Core/fil.json | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json index 1a3e188327..e5ca676a48 100644 --- a/Emby.Server.Implementations/Localization/Core/fil.json +++ b/Emby.Server.Implementations/Localization/Core/fil.json @@ -1,7 +1,7 @@ { "VersionNumber": "Bersyon {0}", "ValueSpecialEpisodeName": "Espesyal - {0}", - "ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong media library", + "ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong librerya ng medya", "UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}", "UserStartedPlayingItemWithValues": "Si {0} ay nagplaplay ng {1} sa {2}", "UserPolicyUpdatedWithName": "Ang user policy ay naiupdate para kay {0}", @@ -61,8 +61,8 @@ "Latest": "Pinakabago", "LabelRunningTimeValue": "Oras: {0}", "LabelIpAddressValue": "Ang IP Address ay {0}", - "ItemRemovedWithName": "Naitanggal ang {0} sa library", - "ItemAddedWithName": "Naidagdag ang {0} sa library", + "ItemRemovedWithName": "Naitanggal ang {0} sa librerya", + "ItemAddedWithName": "Naidagdag ang {0} sa librerya", "Inherit": "Manahin", "HeaderRecordingGroups": "Pagtatalang Grupo", "HeaderNextUp": "Susunod", @@ -90,12 +90,29 @@ "Application": "Aplikasyon", "AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}", "Albums": "Albums", - "TaskRefreshLibrary": "Suriin ang nasa librerya", - "TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata", + "TaskRefreshLibrary": "Suriin and Librerya ng Medya", + "TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata.", "TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata", "TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.", "TasksChannelsCategory": "Palabas sa internet", "TasksLibraryCategory": "Librerya", "TasksMaintenanceCategory": "Pagpapanatili", - "HomeVideos": "Sariling pelikula" + "HomeVideos": "Sariling pelikula", + "TaskRefreshPeopleDescription": "Ini-update ang metadata para sa mga aktor at direktor sa iyong librerya ng medya.", + "TaskRefreshPeople": "I-refresh ang Tauhan", + "TaskDownloadMissingSubtitlesDescription": "Hinahanap sa internet ang mga nawawalang subtiles base sa metadata configuration.", + "TaskDownloadMissingSubtitles": "I-download and nawawalang subtitles", + "TaskRefreshChannelsDescription": "Ni-rerefresh ang impormasyon sa internet channels.", + "TaskRefreshChannels": "I-refresh ang Channels", + "TaskCleanTranscodeDescription": "Binubura ang transcode files na mas matanda ng isang araw.", + "TaskUpdatePluginsDescription": "Nag download at install ng updates sa plugins na naka configure para sa automatikong pag update.", + "TaskUpdatePlugins": "I-update ang Plugins", + "TaskCleanLogsDescription": "Binubura and files ng talaan na mas mantanda ng {0} araw.", + "TaskCleanTranscode": "Linisin and Direktoryo ng Transcode", + "TaskCleanLogs": "Linisin and Direktoryo ng Talaan", + "TaskRefreshLibraryDescription": "Sinusuri ang iyong librerya ng medya para sa bagong files at irefresh ang metadata.", + "TaskCleanCache": "Linisin and Direktoryo ng Cache", + "TasksApplicationCategory": "Application", + "TaskCleanActivityLog": "Linisin ang Tala ng Aktibidad", + "TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas matanda sa naka configure na edad." } From 51ddf038dc3604b53488c618adb728f374816db1 Mon Sep 17 00:00:00 2001 From: Ted van den Brink Date: Tue, 10 Nov 2020 22:44:05 +0000 Subject: [PATCH 201/203] 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 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index e102b92b90..e1e88cc9ba 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -113,5 +113,7 @@ "TasksChannelsCategory": "Internet Kanalen", "TasksApplicationCategory": "Applicatie", "TasksLibraryCategory": "Bibliotheek", - "TasksMaintenanceCategory": "Onderhoud" + "TasksMaintenanceCategory": "Onderhoud", + "TaskCleanActivityLogDescription": "Verwijder activiteiten logs ouder dan de ingestelde tijd.", + "TaskCleanActivityLog": "Leeg activiteiten logboek" } From 0c45faf100d226a00c07e785aa55e22ec55bda9c Mon Sep 17 00:00:00 2001 From: Sam Cross Date: Wed, 11 Nov 2020 00:27:35 +0000 Subject: [PATCH 202/203] 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 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 57ff13219f..cd64cdde45 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -113,5 +113,7 @@ "TasksChannelsCategory": "Internet Channels", "TasksApplicationCategory": "Application", "TasksLibraryCategory": "Library", - "TasksMaintenanceCategory": "Maintenance" + "TasksMaintenanceCategory": "Maintenance", + "TaskCleanActivityLogDescription": "Deletes activity log entries older than the configured age.", + "TaskCleanActivityLog": "Clean Activity Log" } From 0b954df7c2052be74792b1e7ec6c46855e36f4b2 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 11 Nov 2020 10:49:00 +0100 Subject: [PATCH 203/203] Update Jellyfin.Api/Controllers/LibraryController.cs --- Jellyfin.Api/Controllers/LibraryController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 99fbfb4026..0d5c1d2782 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -710,8 +710,7 @@ namespace Jellyfin.Api.Controllers var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; - var dtoOptions = new DtoOptions() - .AddItemFields(fields) + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request); var program = item as IHasProgramAttributes;