Переглянути джерело

Merge branch 'master' into authenticationdb-efcore

# Conflicts:
#	Emby.Server.Implementations/Security/AuthenticationRepository.cs
#	Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
#	MediaBrowser.Controller/Devices/IDeviceManager.cs
Patrick Barron 4 роки тому
батько
коміт
b6446c06ee
100 змінених файлів з 1253 додано та 1319 видалено
  1. 5 5
      .github/workflows/automation.yml
  2. 1 1
      .github/workflows/merge-conflicts.yml
  3. 3 3
      .github/workflows/rebase.yml
  4. 3 3
      Emby.Naming/Audio/AudioFileParser.cs
  5. 3 2
      Emby.Naming/Emby.Naming.csproj
  6. 49 46
      Emby.Naming/Video/ExtraResolver.cs
  7. 10 12
      Emby.Naming/Video/VideoResolver.cs
  8. 2 4
      Emby.Server.Implementations/Data/BaseSqliteRepository.cs
  9. 101 10
      Emby.Server.Implementations/Data/SqliteExtensions.cs
  10. 222 332
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  11. 8 8
      Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
  12. 1 1
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  13. 8 8
      Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
  14. 2 12
      Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
  15. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  16. 5 3
      Emby.Server.Implementations/Localization/Core/bg-BG.json
  17. 1 1
      Emby.Server.Implementations/Localization/Core/bn.json
  18. 26 28
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  19. 0 1
      Jellyfin.Api/Helpers/TranscodingJobHelper.cs
  20. 1 1
      Jellyfin.Api/Jellyfin.Api.csproj
  21. 4 4
      Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
  22. 18 22
      Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
  23. 2 2
      Jellyfin.Server/Jellyfin.Server.csproj
  24. 7 0
      Jellyfin.sln
  25. 51 0
      MediaBrowser.Common/Extensions/EnumerableExtensions.cs
  26. 1 1
      MediaBrowser.Controller/Channels/Channel.cs
  27. 24 4
      MediaBrowser.Controller/Channels/IChannelManager.cs
  28. 8 0
      MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs
  29. 9 0
      MediaBrowser.Controller/Channels/IHasFolderAttributes.cs
  30. 6 2
      MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs
  31. 0 32
      MediaBrowser.Controller/Channels/ISearchableChannel.cs
  32. 15 0
      MediaBrowser.Controller/Channels/ISupportsDelete.cs
  33. 21 0
      MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs
  34. 8 0
      MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs
  35. 2 2
      MediaBrowser.Controller/Channels/InternalChannelFeatures.cs
  36. 6 0
      MediaBrowser.Controller/Dto/IDtoService.cs
  37. 4 4
      MediaBrowser.Controller/Entities/AggregateFolder.cs
  38. 2 2
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  39. 3 3
      MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
  40. 24 15
      MediaBrowser.Controller/Entities/BaseItem.cs
  41. 38 28
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  42. 84 86
      MediaBrowser.Controller/Entities/Folder.cs
  43. 20 22
      MediaBrowser.Controller/Entities/Genre.cs
  44. 1 1
      MediaBrowser.Controller/Entities/IHasSeries.cs
  45. 11 0
      MediaBrowser.Controller/Entities/IHasShares.cs
  46. 41 44
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  47. 2 2
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  48. 2 2
      MediaBrowser.Controller/Entities/MusicVideo.cs
  49. 6 3
      MediaBrowser.Controller/Entities/Person.cs
  50. 0 5
      MediaBrowser.Controller/Entities/Share.cs
  51. 3 3
      MediaBrowser.Controller/Entities/Studio.cs
  52. 7 6
      MediaBrowser.Controller/Entities/TV/Episode.cs
  53. 3 3
      MediaBrowser.Controller/Entities/TV/Season.cs
  54. 4 1
      MediaBrowser.Controller/Entities/TV/Series.cs
  55. 2 2
      MediaBrowser.Controller/Entities/Trailer.cs
  56. 1 1
      MediaBrowser.Controller/Entities/UserItemData.cs
  57. 3 2
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  58. 13 7
      MediaBrowser.Controller/Entities/UserView.cs
  59. 6 4
      MediaBrowser.Controller/Entities/Year.cs
  60. 1 1
      MediaBrowser.Controller/Extensions/StringExtensions.cs
  61. 6 0
      MediaBrowser.Controller/Library/ILibraryManager.cs
  62. 1 1
      MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
  63. 1 1
      MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
  64. 7 8
      MediaBrowser.Controller/LiveTv/TimerInfo.cs
  65. 4 3
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  66. 5 26
      MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
  67. 0 49
      MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
  68. 1 1
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  69. 23 0
      MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs
  70. 8 8
      MediaBrowser.Controller/Playlists/Playlist.cs
  71. 9 0
      MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs
  72. 1 7
      MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
  73. 1 1
      MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
  74. 0 22
      MediaBrowser.Controller/Sync/IHasDynamicAccess.cs
  75. 0 9
      MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs
  76. 0 32
      MediaBrowser.Controller/Sync/IServerSyncProvider.cs
  77. 0 31
      MediaBrowser.Controller/Sync/ISyncProvider.cs
  78. 0 43
      MediaBrowser.Controller/Sync/SyncedFileInfo.cs
  79. 10 0
      MediaBrowser.Controller/TV/ITVSeriesManager.cs
  80. 14 3
      MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
  81. 67 68
      MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
  82. 37 45
      MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
  83. 76 94
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
  84. 2 3
      MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
  85. 2 3
      MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
  86. 4 2
      MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
  87. 6 0
      MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
  88. 9 13
      MediaBrowser.Providers/Studios/StudiosImageProvider.cs
  89. 33 37
      MediaBrowser.Providers/Subtitles/SubtitleManager.cs
  90. 1 1
      deployment/Dockerfile.debian.amd64
  91. 1 1
      deployment/Dockerfile.debian.arm64
  92. 1 1
      deployment/Dockerfile.debian.armhf
  93. 1 1
      deployment/Dockerfile.linux.amd64
  94. 1 1
      deployment/Dockerfile.linux.amd64-musl
  95. 1 1
      deployment/Dockerfile.linux.arm64
  96. 1 1
      deployment/Dockerfile.linux.armhf
  97. 1 1
      deployment/Dockerfile.macos
  98. 1 1
      deployment/Dockerfile.portable
  99. 1 1
      deployment/Dockerfile.ubuntu.amd64
  100. 1 1
      deployment/Dockerfile.ubuntu.arm64

+ 5 - 5
.github/workflows/automation.yml

@@ -20,7 +20,7 @@ jobs:
         with:
         with:
           project: Current Release
           project: Current Release
           action: delete
           action: delete
-          repo-token: ${{ secrets.GH_TOKEN }}
+          repo-token: ${{ secrets.JF_BOT_TOKEN }}
 
 
       - name: Add to 'Release Next' project
       - name: Add to 'Release Next' project
         uses: alex-page/github-project-automation-plus@v0.7.1
         uses: alex-page/github-project-automation-plus@v0.7.1
@@ -29,7 +29,7 @@ jobs:
         with:
         with:
           project: Release Next
           project: Release Next
           column: In progress
           column: In progress
-          repo-token: ${{ secrets.GH_TOKEN }}
+          repo-token: ${{ secrets.JF_BOT_TOKEN }}
 
 
       - name: Add to 'Current Release' project
       - name: Add to 'Current Release' project
         uses: alex-page/github-project-automation-plus@v0.7.1
         uses: alex-page/github-project-automation-plus@v0.7.1
@@ -38,7 +38,7 @@ jobs:
         with:
         with:
           project: Current Release
           project: Current Release
           column: In progress
           column: In progress
-          repo-token: ${{ secrets.GH_TOKEN }}
+          repo-token: ${{ secrets.JF_BOT_TOKEN }}
 
 
       - name: Check number of comments from the team member
       - name: Check number of comments from the team member
         if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
         if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
@@ -52,7 +52,7 @@ jobs:
         with:
         with:
           project: Issue Triage for Main Repo
           project: Issue Triage for Main Repo
           column: Needs triage
           column: Needs triage
-          repo-token: ${{ secrets.GH_TOKEN }}
+          repo-token: ${{ secrets.JF_BOT_TOKEN }}
 
 
       - name: Add issue to triage project
       - name: Add issue to triage project
         uses: alex-page/github-project-automation-plus@v0.7.1
         uses: alex-page/github-project-automation-plus@v0.7.1
@@ -61,4 +61,4 @@ jobs:
         with:
         with:
           project: Issue Triage for Main Repo
           project: Issue Triage for Main Repo
           column: Pending response
           column: Pending response
-          repo-token: ${{ secrets.GH_TOKEN }}
+          repo-token: ${{ secrets.JF_BOT_TOKEN }}

+ 1 - 1
.github/workflows/merge-conflicts.yml

@@ -14,4 +14,4 @@ jobs:
       - uses: eps1lon/actions-label-merge-conflict@v2.0.1
       - uses: eps1lon/actions-label-merge-conflict@v2.0.1
         with:
         with:
           dirtyLabel: 'merge conflict'
           dirtyLabel: 'merge conflict'
-          repoToken: ${{ secrets.GH_TOKEN }}
+          repoToken: ${{ secrets.JF_BOT_TOKEN }}

+ 3 - 3
.github/workflows/rebase.yml

@@ -11,17 +11,17 @@ jobs:
       - name: Notify as seen
       - name: Notify as seen
         uses: peter-evans/create-or-update-comment@v1.4.5
         uses: peter-evans/create-or-update-comment@v1.4.5
         with:
         with:
-          token: ${{ secrets.GH_TOKEN }}
+          token: ${{ secrets.JF_BOT_TOKEN }}
           comment-id: ${{ github.event.comment.id }}
           comment-id: ${{ github.event.comment.id }}
           reactions: '+1'
           reactions: '+1'
 
 
       - name: Checkout the latest code
       - name: Checkout the latest code
         uses: actions/checkout@v2
         uses: actions/checkout@v2
         with:
         with:
-          token: ${{ secrets.GH_TOKEN }}
+          token: ${{ secrets.JF_BOT_TOKEN }}
           fetch-depth: 0
           fetch-depth: 0
 
 
       - name: Automatic Rebase
       - name: Automatic Rebase
         uses: cirrus-actions/rebase@1.4
         uses: cirrus-actions/rebase@1.4
         env:
         env:
-          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
+          GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}

+ 3 - 3
Emby.Naming/Audio/AudioFileParser.cs

@@ -1,7 +1,7 @@
 using System;
 using System;
 using System.IO;
 using System.IO;
-using System.Linq;
 using Emby.Naming.Common;
 using Emby.Naming.Common;
+using MediaBrowser.Common.Extensions;
 
 
 namespace Emby.Naming.Audio
 namespace Emby.Naming.Audio
 {
 {
@@ -18,8 +18,8 @@ namespace Emby.Naming.Audio
         /// <returns>True if file at path is audio file.</returns>
         /// <returns>True if file at path is audio file.</returns>
         public static bool IsAudioFile(string path, NamingOptions options)
         public static bool IsAudioFile(string path, NamingOptions options)
         {
         {
-            var extension = Path.GetExtension(path);
-            return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+            var extension = Path.GetExtension(path.AsSpan());
+            return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
         }
         }
     }
     }
 }
 }

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

@@ -23,11 +23,12 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <Compile Include="..\SharedVersion.cs" />
+    <Compile Include="../SharedVersion.cs" />
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
+    <ProjectReference Include="../MediaBrowser.Common/MediaBrowser.Common.csproj" />
+    <ProjectReference Include="../MediaBrowser.Model/MediaBrowser.Model.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
   <PropertyGroup>
   <PropertyGroup>

+ 49 - 46
Emby.Naming/Video/ExtraResolver.cs

@@ -29,70 +29,73 @@ namespace Emby.Naming.Video
         /// <param name="path">Path to file.</param>
         /// <param name="path">Path to file.</param>
         /// <returns>Returns <see cref="ExtraResult"/> object.</returns>
         /// <returns>Returns <see cref="ExtraResult"/> object.</returns>
         public ExtraResult GetExtraInfo(string path)
         public ExtraResult GetExtraInfo(string path)
-        {
-            return _options.VideoExtraRules
-                .Select(i => GetExtraInfo(path, i))
-                .FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
-        }
-
-        private ExtraResult GetExtraInfo(string path, ExtraRule rule)
         {
         {
             var result = new ExtraResult();
             var result = new ExtraResult();
 
 
-            if (rule.MediaType == MediaType.Audio)
+            for (var i = 0; i < _options.VideoExtraRules.Length; i++)
             {
             {
-                if (!AudioFileParser.IsAudioFile(path, _options))
+                var rule = _options.VideoExtraRules[i];
+                if (rule.MediaType == MediaType.Audio)
                 {
                 {
-                    return result;
+                    if (!AudioFileParser.IsAudioFile(path, _options))
+                    {
+                        continue;
+                    }
                 }
                 }
-            }
-            else if (rule.MediaType == MediaType.Video)
-            {
-                if (!new VideoResolver(_options).IsVideoFile(path))
+                else if (rule.MediaType == MediaType.Video)
                 {
                 {
-                    return result;
+                    if (!new VideoResolver(_options).IsVideoFile(path))
+                    {
+                        continue;
+                    }
                 }
                 }
-            }
-
-            if (rule.RuleType == ExtraRuleType.Filename)
-            {
-                var filename = Path.GetFileNameWithoutExtension(path);
 
 
-                if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase))
+                var pathSpan = path.AsSpan();
+                if (rule.RuleType == ExtraRuleType.Filename)
                 {
                 {
-                    result.ExtraType = rule.ExtraType;
-                    result.Rule = rule;
-                }
-            }
-            else if (rule.RuleType == ExtraRuleType.Suffix)
-            {
-                var filename = Path.GetFileNameWithoutExtension(path);
+                    var filename = Path.GetFileNameWithoutExtension(pathSpan);
 
 
-                if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0)
+                    if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
+                    {
+                        result.ExtraType = rule.ExtraType;
+                        result.Rule = rule;
+                    }
+                }
+                else if (rule.RuleType == ExtraRuleType.Suffix)
                 {
                 {
-                    result.ExtraType = rule.ExtraType;
-                    result.Rule = rule;
+                    var filename = Path.GetFileNameWithoutExtension(pathSpan);
+
+                    if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase))
+                    {
+                        result.ExtraType = rule.ExtraType;
+                        result.Rule = rule;
+                    }
                 }
                 }
-            }
-            else if (rule.RuleType == ExtraRuleType.Regex)
-            {
-                var filename = Path.GetFileName(path);
+                else if (rule.RuleType == ExtraRuleType.Regex)
+                {
+                    var filename = Path.GetFileName(path);
 
 
-                var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
+                    var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
 
 
-                if (regex.IsMatch(filename))
+                    if (regex.IsMatch(filename))
+                    {
+                        result.ExtraType = rule.ExtraType;
+                        result.Rule = rule;
+                    }
+                }
+                else if (rule.RuleType == ExtraRuleType.DirectoryName)
                 {
                 {
-                    result.ExtraType = rule.ExtraType;
-                    result.Rule = rule;
+                    var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
+                    if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
+                    {
+                        result.ExtraType = rule.ExtraType;
+                        result.Rule = rule;
+                    }
                 }
                 }
-            }
-            else if (rule.RuleType == ExtraRuleType.DirectoryName)
-            {
-                var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
-                if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
+
+                if (result.ExtraType != null)
                 {
                 {
-                    result.ExtraType = rule.ExtraType;
-                    result.Rule = rule;
+                    return result;
                 }
                 }
             }
             }
 
 

+ 10 - 12
Emby.Naming/Video/VideoResolver.cs

@@ -1,8 +1,8 @@
 using System;
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.IO;
-using System.Linq;
 using Emby.Naming.Common;
 using Emby.Naming.Common;
+using MediaBrowser.Common.Extensions;
 
 
 namespace Emby.Naming.Video
 namespace Emby.Naming.Video
 {
 {
@@ -59,15 +59,15 @@ namespace Emby.Naming.Video
             }
             }
 
 
             bool isStub = false;
             bool isStub = false;
-            string? container = null;
+            ReadOnlySpan<char> container = ReadOnlySpan<char>.Empty;
             string? stubType = null;
             string? stubType = null;
 
 
             if (!isDirectory)
             if (!isDirectory)
             {
             {
-                var extension = Path.GetExtension(path);
+                var extension = Path.GetExtension(path.AsSpan());
 
 
                 // Check supported extensions
                 // Check supported extensions
-                if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+                if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     // It's not supported. Check stub extensions
                     // It's not supported. Check stub extensions
                     if (!StubResolver.TryResolveFile(path, _options, out stubType))
                     if (!StubResolver.TryResolveFile(path, _options, out stubType))
@@ -86,9 +86,7 @@ namespace Emby.Naming.Video
 
 
             var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
             var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
 
 
-            var name = isDirectory
-                ? Path.GetFileName(path)
-                : Path.GetFileNameWithoutExtension(path);
+            var name = Path.GetFileNameWithoutExtension(path);
 
 
             int? year = null;
             int? year = null;
 
 
@@ -107,7 +105,7 @@ namespace Emby.Naming.Video
 
 
             return new VideoFileInfo(
             return new VideoFileInfo(
                 path: path,
                 path: path,
-                container: container,
+                container: container.IsEmpty ? null : container.ToString(),
                 isStub: isStub,
                 isStub: isStub,
                 name: name,
                 name: name,
                 year: year,
                 year: year,
@@ -126,8 +124,8 @@ namespace Emby.Naming.Video
         /// <returns>True if is video file.</returns>
         /// <returns>True if is video file.</returns>
         public bool IsVideoFile(string path)
         public bool IsVideoFile(string path)
         {
         {
-            var extension = Path.GetExtension(path);
-            return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+            var extension = Path.GetExtension(path.AsSpan());
+            return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -137,8 +135,8 @@ namespace Emby.Naming.Video
         /// <returns>True if is video file stub.</returns>
         /// <returns>True if is video file stub.</returns>
         public bool IsStubFile(string path)
         public bool IsStubFile(string path)
         {
         {
-            var extension = Path.GetExtension(path);
-            return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+            var extension = Path.GetExtension(path.AsSpan());
+            return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 4
Emby.Server.Implementations/Data/BaseSqliteRepository.cs

@@ -181,11 +181,9 @@ namespace Emby.Server.Implementations.Data
 
 
             foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
             foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
             {
             {
-                if (row[1].SQLiteType != SQLiteType.Null)
+                if (row.TryGetString(1, out var columnName))
                 {
                 {
-                    var name = row[1].ToString();
-
-                    columnNames.Add(name);
+                    columnNames.Add(columnName);
                 }
                 }
             }
             }
 
 

+ 101 - 10
Emby.Server.Implementations/Data/SqliteExtensions.cs

@@ -3,6 +3,7 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
 using SQLitePCL.pretty;
 using SQLitePCL.pretty;
 
 
@@ -96,21 +97,43 @@ namespace Emby.Server.Implementations.Data
                 DateTimeStyles.None).ToUniversalTime();
                 DateTimeStyles.None).ToUniversalTime();
         }
         }
 
 
-        public static DateTime? TryReadDateTime(this IResultSetValue result)
+        public static bool TryReadDateTime(this IReadOnlyList<IResultSetValue> reader, int index, out DateTime result)
         {
         {
-            var dateText = result.ToString();
+            var item = reader[index];
+            if (item.IsDbNull())
+            {
+                result = default;
+                return false;
+            }
+
+            var dateText = item.ToString();
 
 
             if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
             if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
             {
             {
-                return dateTimeResult.ToUniversalTime();
+                result = dateTimeResult.ToUniversalTime();
+                return true;
+            }
+
+            result = default;
+            return false;
+        }
+
+        public static bool TryGetGuid(this IReadOnlyList<IResultSetValue> reader, int index, out Guid result)
+        {
+            var item = reader[index];
+            if (item.IsDbNull())
+            {
+                result = default;
+                return false;
             }
             }
 
 
-            return null;
+            result = item.ReadGuidFromBlob();
+            return true;
         }
         }
 
 
-        public static bool IsDBNull(this IReadOnlyList<IResultSetValue> result, int index)
+        public static bool IsDbNull(this IResultSetValue result)
         {
         {
-            return result[index].SQLiteType == SQLiteType.Null;
+            return result.SQLiteType == SQLiteType.Null;
         }
         }
 
 
         public static string GetString(this IReadOnlyList<IResultSetValue> result, int index)
         public static string GetString(this IReadOnlyList<IResultSetValue> result, int index)
@@ -118,14 +141,48 @@ namespace Emby.Server.Implementations.Data
             return result[index].ToString();
             return result[index].ToString();
         }
         }
 
 
+        public static bool TryGetString(this IReadOnlyList<IResultSetValue> reader, int index, out string result)
+        {
+            result = null;
+            var item = reader[index];
+            if (item.IsDbNull())
+            {
+                return false;
+            }
+
+            result = item.ToString();
+            return true;
+        }
+
         public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index)
         public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index)
         {
         {
             return result[index].ToBool();
             return result[index].ToBool();
         }
         }
 
 
-        public static int GetInt32(this IReadOnlyList<IResultSetValue> result, int index)
+        public static bool TryGetBoolean(this IReadOnlyList<IResultSetValue> reader, int index, out bool result)
+        {
+            var item = reader[index];
+            if (item.IsDbNull())
+            {
+                result = default;
+                return false;
+            }
+
+            result = item.ToBool();
+            return true;
+        }
+
+        public static bool TryGetInt32(this IReadOnlyList<IResultSetValue> reader, int index, out int result)
         {
         {
-            return result[index].ToInt();
+            var item = reader[index];
+            if (item.IsDbNull())
+            {
+                result = default;
+                return false;
+            }
+
+            result = item.ToInt();
+            return true;
         }
         }
 
 
         public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index)
         public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index)
@@ -133,9 +190,43 @@ namespace Emby.Server.Implementations.Data
             return result[index].ToInt64();
             return result[index].ToInt64();
         }
         }
 
 
-        public static float GetFloat(this IReadOnlyList<IResultSetValue> result, int index)
+        public static bool TryGetInt64(this IReadOnlyList<IResultSetValue> reader, int index, out long result)
+        {
+            var item = reader[index];
+            if (item.IsDbNull())
+            {
+                result = default;
+                return false;
+            }
+
+            result = item.ToInt64();
+            return true;
+        }
+
+        public static bool TryGetSingle(this IReadOnlyList<IResultSetValue> reader, int index, out float result)
+        {
+            var item = reader[index];
+            if (item.IsDbNull())
+            {
+                result = default;
+                return false;
+            }
+
+            result = item.ToFloat();
+            return true;
+        }
+
+        public static bool TryGetDouble(this IReadOnlyList<IResultSetValue> reader, int index, out double result)
         {
         {
-            return result[index].ToFloat();
+            var item = reader[index];
+            if (item.IsDbNull())
+            {
+                result = default;
+                return false;
+            }
+
+            result = item.ToDouble();
+            return true;
         }
         }
 
 
         public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index)
         public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index)

Різницю між файлами не показано, бо вона завелика
+ 222 - 332
Emby.Server.Implementations/Data/SqliteItemRepository.cs


+ 8 - 8
Emby.Server.Implementations/Data/SqliteUserDataRepository.cs

@@ -355,9 +355,9 @@ namespace Emby.Server.Implementations.Data
             userData.Key = reader[0].ToString();
             userData.Key = reader[0].ToString();
             // userData.UserId = reader[1].ReadGuidFromBlob();
             // userData.UserId = reader[1].ReadGuidFromBlob();
 
 
-            if (reader[2].SQLiteType != SQLiteType.Null)
+            if (reader.TryGetDouble(2, out var rating))
             {
             {
-                userData.Rating = reader[2].ToDouble();
+                userData.Rating = rating;
             }
             }
 
 
             userData.Played = reader[3].ToBool();
             userData.Played = reader[3].ToBool();
@@ -365,19 +365,19 @@ namespace Emby.Server.Implementations.Data
             userData.IsFavorite = reader[5].ToBool();
             userData.IsFavorite = reader[5].ToBool();
             userData.PlaybackPositionTicks = reader[6].ToInt64();
             userData.PlaybackPositionTicks = reader[6].ToInt64();
 
 
-            if (reader[7].SQLiteType != SQLiteType.Null)
+            if (reader.TryReadDateTime(7, out var lastPlayedDate))
             {
             {
-                userData.LastPlayedDate = reader[7].TryReadDateTime();
+                userData.LastPlayedDate = lastPlayedDate;
             }
             }
 
 
-            if (reader[8].SQLiteType != SQLiteType.Null)
+            if (reader.TryGetInt32(8, out var audioStreamIndex))
             {
             {
-                userData.AudioStreamIndex = reader[8].ToInt();
+                userData.AudioStreamIndex = audioStreamIndex;
             }
             }
 
 
-            if (reader[9].SQLiteType != SQLiteType.Null)
+            if (reader.TryGetInt32(9, out var subtitleStreamIndex))
             {
             {
-                userData.SubtitleStreamIndex = reader[9].ToInt();
+                userData.SubtitleStreamIndex = subtitleStreamIndex;
             }
             }
 
 
             return userData;
             return userData;

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

@@ -27,7 +27,7 @@
     <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
     <PackageReference Include="Mono.Nat" Version="3.0.1" />
     <PackageReference Include="Mono.Nat" Version="3.0.1" />
     <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
     <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
     <PackageReference Include="sharpcompress" Version="0.28.2" />
     <PackageReference Include="sharpcompress" Version="0.28.2" />

+ 8 - 8
Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs

@@ -165,13 +165,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
 
 
         protected void SetVideoType(Video video, VideoFileInfo videoInfo)
         protected void SetVideoType(Video video, VideoFileInfo videoInfo)
         {
         {
-            var extension = Path.GetExtension(video.Path);
-            video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) ||
-                string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ?
-              VideoType.Iso :
-              VideoType.VideoFile;
+            var extension = Path.GetExtension(video.Path.AsSpan());
+            video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase)
+                              || extension.Equals(".img", StringComparison.OrdinalIgnoreCase)
+                ? VideoType.Iso
+                : VideoType.VideoFile;
 
 
-            video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase);
+            video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase);
             video.IsPlaceHolder = videoInfo.IsStub;
             video.IsPlaceHolder = videoInfo.IsStub;
 
 
             if (videoInfo.IsStub)
             if (videoInfo.IsStub)
@@ -193,11 +193,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
         {
         {
             if (video.VideoType == VideoType.Iso)
             if (video.VideoType == VideoType.Iso)
             {
             {
-                if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1)
+                if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     video.IsoType = IsoType.Dvd;
                     video.IsoType = IsoType.Dvd;
                 }
                 }
-                else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1)
+                else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     video.IsoType = IsoType.BluRay;
                     video.IsoType = IsoType.BluRay;
                 }
                 }

+ 2 - 12
Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs

@@ -6,7 +6,7 @@ using MediaBrowser.Controller.LiveTv;
 
 
 namespace Emby.Server.Implementations.LiveTv.EmbyTV
 namespace Emby.Server.Implementations.LiveTv.EmbyTV
 {
 {
-    internal class RecordingHelper
+    internal static class RecordingHelper
     {
     {
         public static DateTime GetStartTime(TimerInfo timer)
         public static DateTime GetStartTime(TimerInfo timer)
         {
         {
@@ -70,17 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
         private static string GetDateString(DateTime date)
         private static string GetDateString(DateTime date)
         {
         {
-            date = date.ToLocalTime();
-
-            return string.Format(
-                CultureInfo.InvariantCulture,
-                "{0}_{1}_{2}_{3}_{4}_{5}",
-                date.Year.ToString("0000", CultureInfo.InvariantCulture),
-                date.Month.ToString("00", CultureInfo.InvariantCulture),
-                date.Day.ToString("00", CultureInfo.InvariantCulture),
-                date.Hour.ToString("00", CultureInfo.InvariantCulture),
-                date.Minute.ToString("00", CultureInfo.InvariantCulture),
-                date.Second.ToString("00", CultureInfo.InvariantCulture));
+            return date.ToLocalTime().ToString("yyyy_MM_dd_HH_mm_ss", CultureInfo.InvariantCulture);
         }
         }
     }
     }
 }
 }

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

@@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         {
         {
             var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
             var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
 
 
-            using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+            using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
             await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
             await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
             var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken)
             var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken)
                 .ConfigureAwait(false) ?? new List<Channels>();
                 .ConfigureAwait(false) ?? new List<Channels>();

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

@@ -39,7 +39,7 @@
     "MixedContent": "Смесено съдържание",
     "MixedContent": "Смесено съдържание",
     "Movies": "Филми",
     "Movies": "Филми",
     "Music": "Музика",
     "Music": "Музика",
-    "MusicVideos": "Музикални клипове",
+    "MusicVideos": "Музикални видеа",
     "NameInstallFailed": "{0} не можа да се инсталира",
     "NameInstallFailed": "{0} не можа да се инсталира",
     "NameSeasonNumber": "Сезон {0}",
     "NameSeasonNumber": "Сезон {0}",
     "NameSeasonUnknown": "Неразпознат сезон",
     "NameSeasonUnknown": "Неразпознат сезон",
@@ -62,7 +62,7 @@
     "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
     "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
     "Photos": "Снимки",
     "Photos": "Снимки",
     "Playlists": "Списъци",
     "Playlists": "Списъци",
-    "Plugin": "Приставка",
+    "Plugin": "Добавка",
     "PluginInstalledWithName": "{0} е инсталиранa",
     "PluginInstalledWithName": "{0} е инсталиранa",
     "PluginUninstalledWithName": "{0} е деинсталиранa",
     "PluginUninstalledWithName": "{0} е деинсталиранa",
     "PluginUpdatedWithName": "{0} е обновенa",
     "PluginUpdatedWithName": "{0} е обновенa",
@@ -116,5 +116,7 @@
     "TasksMaintenanceCategory": "Поддръжка",
     "TasksMaintenanceCategory": "Поддръжка",
     "Undefined": "Неопределено",
     "Undefined": "Неопределено",
     "Forced": "Принудително",
     "Forced": "Принудително",
-    "Default": "По подразбиране"
+    "Default": "По подразбиране",
+    "TaskCleanActivityLogDescription": "Изтрива записите в дневника с активност по стари от конфигурираната възраст.",
+    "TaskCleanActivityLog": "Изчисти дневника с активност"
 }
 }

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

@@ -1,7 +1,7 @@
 {
 {
     "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
     "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
     "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
     "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
-    "Collections": "কলেক্শন",
+    "Collections": "সংগ্রহ",
     "ChapterNameValue": "অধ্যায় {0}",
     "ChapterNameValue": "অধ্যায় {0}",
     "Channels": "চ্যানেল",
     "Channels": "চ্যানেল",
     "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে",
     "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে",

+ 26 - 28
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -28,7 +28,6 @@ using MediaBrowser.Model.Net;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 using Microsoft.Net.Http.Headers;
 using Microsoft.Net.Http.Headers;
 
 
@@ -545,7 +544,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] EncodingContext? context,
             [FromQuery] EncodingContext? context,
             [FromQuery] Dictionary<string, string> streamOptions)
             [FromQuery] Dictionary<string, string> streamOptions)
         {
         {
-            var cancellationTokenSource = new CancellationTokenSource();
+            using var cancellationTokenSource = new CancellationTokenSource();
             var streamingRequest = new VideoRequestDto
             var streamingRequest = new VideoRequestDto
             {
             {
                 Id = itemId,
                 Id = itemId,
@@ -710,7 +709,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] EncodingContext? context,
             [FromQuery] EncodingContext? context,
             [FromQuery] Dictionary<string, string> streamOptions)
             [FromQuery] Dictionary<string, string> streamOptions)
         {
         {
-            var cancellationTokenSource = new CancellationTokenSource();
+            using var cancellationTokenSource = new CancellationTokenSource();
             var streamingRequest = new StreamingRequestDto
             var streamingRequest = new StreamingRequestDto
             {
             {
                 Id = itemId,
                 Id = itemId,
@@ -1138,7 +1137,7 @@ namespace Jellyfin.Api.Controllers
             var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase);
             var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase);
             var hlsVersion = isHlsInFmp4 ? "7" : "3";
             var hlsVersion = isHlsInFmp4 ? "7" : "3";
 
 
-            var builder = new StringBuilder();
+            var builder = new StringBuilder(128);
 
 
             builder.AppendLine("#EXTM3U")
             builder.AppendLine("#EXTM3U")
                 .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
                 .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
@@ -1191,7 +1190,7 @@ namespace Jellyfin.Api.Controllers
                 throw new ArgumentException("StartTimeTicks is not allowed.");
                 throw new ArgumentException("StartTimeTicks is not allowed.");
             }
             }
 
 
-            var cancellationTokenSource = new CancellationTokenSource();
+            using var cancellationTokenSource = new CancellationTokenSource();
             var cancellationToken = cancellationTokenSource.Token;
             var cancellationToken = cancellationTokenSource.Token;
 
 
             using var state = await StreamingHelpers.GetStreamingState(
             using var state = await StreamingHelpers.GetStreamingState(
@@ -1208,7 +1207,7 @@ namespace Jellyfin.Api.Controllers
                     _deviceManager,
                     _deviceManager,
                     _transcodingJobHelper,
                     _transcodingJobHelper,
                     TranscodingJobType,
                     TranscodingJobType,
-                    cancellationTokenSource.Token)
+                    cancellationToken)
                 .ConfigureAwait(false);
                 .ConfigureAwait(false);
 
 
             var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
             var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
@@ -1227,7 +1226,7 @@ namespace Jellyfin.Api.Controllers
             }
             }
 
 
             var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
             var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
-            await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
+            await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
             var released = false;
             var released = false;
             var startTranscoding = false;
             var startTranscoding = false;
 
 
@@ -1323,24 +1322,28 @@ namespace Jellyfin.Api.Controllers
             return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
             return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
         }
         }
 
 
-        private double[] GetSegmentLengths(StreamState state)
-        {
-            var result = new List<double>();
-
-            var ticks = state.RunTimeTicks ?? 0;
+        private static double[] GetSegmentLengths(StreamState state)
+            => GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength);
 
 
-            var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
+        internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength)
+        {
+            var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks;
+            var wholeSegments = runtimeTicks / segmentLengthTicks;
+            var remainingTicks = runtimeTicks % segmentLengthTicks;
 
 
-            while (ticks > 0)
+            var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1);
+            var segments = new double[segmentsLen];
+            for (int i = 0; i < wholeSegments; i++)
             {
             {
-                var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
-
-                result.Add(TimeSpan.FromTicks(length).TotalSeconds);
+                segments[i] = segmentlength;
+            }
 
 
-                ticks -= length;
+            if (remainingTicks != 0)
+            {
+                segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds;
             }
             }
 
 
-            return result.ToArray();
+            return segments;
         }
         }
 
 
         private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber)
         private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber)
@@ -1376,18 +1379,13 @@ namespace Jellyfin.Api.Controllers
             }
             }
             else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
             else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
             {
             {
-                var outputFmp4HeaderArg = string.Empty;
-                var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-                if (isWindows)
+                var outputFmp4HeaderArg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) switch
                 {
                 {
                     // on Windows, the path of fmp4 header file needs to be configured
                     // on Windows, the path of fmp4 header file needs to be configured
-                    outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"";
-                }
-                else
-                {
+                    true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"",
                     // on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
                     // on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
-                    outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\"";
-                }
+                    false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
+                };
 
 
                 segmentFormat = "fmp4" + outputFmp4HeaderArg;
                 segmentFormat = "fmp4" + outputFmp4HeaderArg;
             }
             }

+ 0 - 1
Jellyfin.Api/Helpers/TranscodingJobHelper.cs

@@ -22,7 +22,6 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.Session;
 using MediaBrowser.Model.Session;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
 namespace Jellyfin.Api.Helpers
 namespace Jellyfin.Api.Helpers

+ 1 - 1
Jellyfin.Api/Jellyfin.Api.csproj

@@ -15,7 +15,7 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.5" />
+    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.6" />
     <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
     <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.4" />
     <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.4" />

+ 4 - 4
Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj

@@ -27,13 +27,13 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="System.Linq.Async" Version="5.0.0" />
     <PackageReference Include="System.Linq.Async" Version="5.0.0" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.5" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.5" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5">
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.6" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.6">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.5">
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.6">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>

+ 18 - 22
Jellyfin.Server.Implementations/Security/AuthorizationContext.cs

@@ -5,6 +5,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Net;
 using System.Net;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
@@ -207,7 +208,7 @@ namespace Jellyfin.Server.Implementations.Security
                 auth = httpReq.Request.Headers[HeaderNames.Authorization];
                 auth = httpReq.Request.Headers[HeaderNames.Authorization];
             }
             }
 
 
-            return GetAuthorization(auth);
+            return auth.Count > 0 ? GetAuthorization(auth[0]) : null;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -224,7 +225,7 @@ namespace Jellyfin.Server.Implementations.Security
                 auth = httpReq.Headers[HeaderNames.Authorization];
                 auth = httpReq.Headers[HeaderNames.Authorization];
             }
             }
 
 
-            return GetAuthorization(auth);
+            return auth.Count > 0 ? GetAuthorization(auth[0]) : null;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -232,43 +233,38 @@ namespace Jellyfin.Server.Implementations.Security
         /// </summary>
         /// </summary>
         /// <param name="authorizationHeader">The authorization header.</param>
         /// <param name="authorizationHeader">The authorization header.</param>
         /// <returns>Dictionary{System.StringSystem.String}.</returns>
         /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        private Dictionary<string, string>? GetAuthorization(string? authorizationHeader)
+        private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader)
         {
         {
-            if (authorizationHeader == null)
-            {
-                return null;
-            }
-
-            var parts = authorizationHeader.Split(' ', 2);
+            var firstSpace = authorizationHeader.IndexOf(' ');
 
 
-            // There should be at least to parts
-            if (parts.Length != 2)
+            // There should be at least two parts
+            if (firstSpace == -1)
             {
             {
                 return null;
                 return null;
             }
             }
 
 
-            var acceptedNames = new[] { "MediaBrowser", "Emby" };
+            var name = authorizationHeader[..firstSpace];
 
 
-            // It has to be a digest request
-            if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase))
+            if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase)
+                && !name.Equals("Emby", StringComparison.OrdinalIgnoreCase))
             {
             {
                 return null;
                 return null;
             }
             }
 
 
-            // Remove uptil the first space
-            authorizationHeader = parts[1];
-            parts = authorizationHeader.Split(',');
+            authorizationHeader = authorizationHeader[(firstSpace + 1)..];
 
 
             var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
 
-            foreach (var item in parts)
+            foreach (var item in authorizationHeader.Split(','))
             {
             {
-                var param = item.Trim().Split('=', 2);
+                var trimmedItem = item.Trim();
+                var firstEqualsSign = trimmedItem.IndexOf('=');
 
 
-                if (param.Length == 2)
+                if (firstEqualsSign > 0)
                 {
                 {
-                    var value = NormalizeValue(param[1].Trim('"'));
-                    result[param[0]] = value;
+                    var key = trimmedItem[..firstEqualsSign].ToString();
+                    var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
+                    result[key] = value;
                 }
                 }
             }
             }
 
 

+ 2 - 2
Jellyfin.Server/Jellyfin.Server.csproj

@@ -38,8 +38,8 @@
     <PackageReference Include="CommandLineParser" Version="2.8.0" />
     <PackageReference Include="CommandLineParser" Version="2.8.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
-    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.5" />
-    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.5" />
+    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.6" />
+    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.6" />
     <PackageReference Include="prometheus-net" Version="4.1.1" />
     <PackageReference Include="prometheus-net" Version="4.1.1" />
     <PackageReference Include="prometheus-net.AspNetCore" Version="4.1.1" />
     <PackageReference Include="prometheus-net.AspNetCore" Version="4.1.1" />
     <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
     <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />

+ 7 - 0
Jellyfin.sln

@@ -81,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "te
 EndProject
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -223,6 +225,10 @@ Global
 		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
 		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -240,6 +246,7 @@ Global
 		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 		{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+		{A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
 		SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}

+ 51 - 0
MediaBrowser.Common/Extensions/EnumerableExtensions.cs

@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Common.Extensions
+{
+    /// <summary>
+    /// Static extensions for the <see cref="IEnumerable{T}"/> interface.
+    /// </summary>
+    public static class EnumerableExtensions
+    {
+        /// <summary>
+        /// Determines whether the value is contained in the source collection.
+        /// </summary>
+        /// <param name="source">An instance of the <see cref="IEnumerable{String}"/> interface.</param>
+        /// <param name="value">The value to look for in the collection.</param>
+        /// <param name="stringComparison">The string comparison.</param>
+        /// <returns>A value indicating whether the value is contained in the collection.</returns>
+        /// <exception cref="ArgumentNullException">The source is null.</exception>
+        public static bool Contains(this IEnumerable<string> source, ReadOnlySpan<char> value, StringComparison stringComparison)
+        {
+            if (source == null)
+            {
+                throw new ArgumentNullException(nameof(source));
+            }
+
+            if (source is IList<string> list)
+            {
+                int len = list.Count;
+                for (int i = 0; i < len; i++)
+                {
+                    if (value.Equals(list[i], stringComparison))
+                    {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+
+            foreach (string element in source)
+            {
+                if (value.Equals(element, stringComparison))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Controller/Channels/Channel.cs

@@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Channels
 
 
         internal static bool IsChannelVisible(BaseItem channelItem, User user)
         internal static bool IsChannelVisible(BaseItem channelItem, User user)
         {
         {
-            var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString(""));
+            var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString(string.Empty));
 
 
             return channel.IsVisible(user);
             return channel.IsVisible(user);
         }
         }

+ 24 - 4
MediaBrowser.Controller/Channels/IChannelManager.cs

@@ -51,32 +51,47 @@ namespace MediaBrowser.Controller.Channels
         /// Gets the channels internal.
         /// Gets the channels internal.
         /// </summary>
         /// </summary>
         /// <param name="query">The query.</param>
         /// <param name="query">The query.</param>
+        /// <returns>The channels.</returns>
         QueryResult<Channel> GetChannelsInternal(ChannelQuery query);
         QueryResult<Channel> GetChannelsInternal(ChannelQuery query);
 
 
         /// <summary>
         /// <summary>
         /// Gets the channels.
         /// Gets the channels.
         /// </summary>
         /// </summary>
         /// <param name="query">The query.</param>
         /// <param name="query">The query.</param>
+        /// <returns>The channels.</returns>
         QueryResult<BaseItemDto> GetChannels(ChannelQuery query);
         QueryResult<BaseItemDto> GetChannels(ChannelQuery query);
 
 
         /// <summary>
         /// <summary>
-        /// Gets the latest media.
+        /// Gets the latest channel items.
         /// </summary>
         /// </summary>
+        /// <param name="query">The item query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The latest channels.</returns>
         Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken);
         Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
-        /// Gets the latest media.
+        /// Gets the latest channel items.
         /// </summary>
         /// </summary>
+        /// <param name="query">The item query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The latest channels.</returns>
         Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken);
         Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Gets the channel items.
         /// Gets the channel items.
         /// </summary>
         /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The channel items.</returns>
         Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken);
         Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
-        /// Gets the channel items internal.
+        /// Gets the channel items.
         /// </summary>
         /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="progress">The progress to report to.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The channel items.</returns>
         Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken);
         Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
@@ -84,9 +99,14 @@ namespace MediaBrowser.Controller.Channels
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
+        /// <returns>The item media sources.</returns>
         IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken);
         IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken);
 
 
+        /// <summary>
+        /// Whether the item supports media probe.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>Whether media probe should be enabled.</returns>
         bool EnableMediaProbe(BaseItem item);
         bool EnableMediaProbe(BaseItem item);
     }
     }
 }
 }

+ 8 - 0
MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs

@@ -0,0 +1,8 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Controller.Channels
+{
+    public interface IDisableMediaSourceDisplay
+    {
+    }
+}

+ 9 - 0
MediaBrowser.Controller/Channels/IHasFolderAttributes.cs

@@ -0,0 +1,9 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Controller.Channels
+{
+    public interface IHasFolderAttributes
+    {
+        string[] Attributes { get; }
+    }
+}

+ 6 - 2
MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs

@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -7,11 +5,17 @@ using MediaBrowser.Model.Dto;
 
 
 namespace MediaBrowser.Controller.Channels
 namespace MediaBrowser.Controller.Channels
 {
 {
+    /// <summary>
+    /// The channel requires a media info callback.
+    /// </summary>
     public interface IRequiresMediaInfoCallback
     public interface IRequiresMediaInfoCallback
     {
     {
         /// <summary>
         /// <summary>
         /// Gets the channel item media information.
         /// Gets the channel item media information.
         /// </summary>
         /// </summary>
+        /// <param name="id">The channel item id.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The enumerable of media source info.</returns>
         Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken);
         Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken);
     }
     }
 }
 }

+ 0 - 32
MediaBrowser.Controller/Channels/ISearchableChannel.cs

@@ -5,7 +5,6 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
 
 
 namespace MediaBrowser.Controller.Channels
 namespace MediaBrowser.Controller.Channels
 {
 {
@@ -19,35 +18,4 @@ namespace MediaBrowser.Controller.Channels
         /// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns>
         /// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns>
         Task<IEnumerable<ChannelItemInfo>> Search(ChannelSearchInfo searchInfo, CancellationToken cancellationToken);
         Task<IEnumerable<ChannelItemInfo>> Search(ChannelSearchInfo searchInfo, CancellationToken cancellationToken);
     }
     }
-
-    public interface ISupportsLatestMedia
-    {
-        /// <summary>
-        /// Gets the latest media.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns>
-        Task<IEnumerable<ChannelItemInfo>> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken);
-    }
-
-    public interface ISupportsDelete
-    {
-        bool CanDelete(BaseItem item);
-
-        Task DeleteItem(string id, CancellationToken cancellationToken);
-    }
-
-    public interface IDisableMediaSourceDisplay
-    {
-    }
-
-    public interface ISupportsMediaProbe
-    {
-    }
-
-    public interface IHasFolderAttributes
-    {
-        string[] Attributes { get; }
-    }
 }
 }

+ 15 - 0
MediaBrowser.Controller/Channels/ISupportsDelete.cs

@@ -0,0 +1,15 @@
+#pragma warning disable CS1591
+
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Channels
+{
+    public interface ISupportsDelete
+    {
+        bool CanDelete(BaseItem item);
+
+        Task DeleteItem(string id, CancellationToken cancellationToken);
+    }
+}

+ 21 - 0
MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs

@@ -0,0 +1,21 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Channels
+{
+    public interface ISupportsLatestMedia
+    {
+        /// <summary>
+        /// Gets the latest media.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The latest media.</returns>
+        Task<IEnumerable<ChannelItemInfo>> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken);
+    }
+}

+ 8 - 0
MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs

@@ -0,0 +1,8 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Controller.Channels
+{
+    public interface ISupportsMediaProbe
+    {
+    }
+}

+ 2 - 2
MediaBrowser.Controller/Channels/InternalChannelFeatures.cs

@@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Channels
         public List<ChannelMediaContentType> ContentTypes { get; set; }
         public List<ChannelMediaContentType> ContentTypes { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Represents the maximum number of records the channel allows retrieving at a time.
+        /// Gets or sets the maximum number of records the channel allows retrieving at a time.
         /// </summary>
         /// </summary>
         public int? MaxPageSize { get; set; }
         public int? MaxPageSize { get; set; }
 
 
@@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.Channels
         public List<ChannelItemSortField> DefaultSortFields { get; set; }
         public List<ChannelItemSortField> DefaultSortFields { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Indicates if a sort ascending/descending toggle is supported or not.
+        /// Gets or sets a value indicating whether a sort ascending/descending toggle is supported or not.
         /// </summary>
         /// </summary>
         public bool SupportsSortOrderToggle { get; set; }
         public bool SupportsSortOrderToggle { get; set; }
 
 

+ 6 - 0
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -36,11 +36,17 @@ namespace MediaBrowser.Controller.Dto
         /// <param name="options">The options.</param>
         /// <param name="options">The options.</param>
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <param name="owner">The owner.</param>
         /// <param name="owner">The owner.</param>
+        /// <returns>The <see cref="IReadOnlyList{T}"/> of <see cref="BaseItemDto"/>.</returns>
         IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null);
         IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null);
 
 
         /// <summary>
         /// <summary>
         /// Gets the item by name dto.
         /// Gets the item by name dto.
         /// </summary>
         /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="options">The dto options.</param>
+        /// <param name="taggedItems">The list of tagged items.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>The item dto.</returns>
         BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null);
         BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null);
     }
     }
 }
 }

+ 4 - 4
MediaBrowser.Controller/Entities/AggregateFolder.cs

@@ -22,6 +22,8 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class AggregateFolder : Folder
     public class AggregateFolder : Folder
     {
     {
+        private bool _requiresRefresh;
+
         public AggregateFolder()
         public AggregateFolder()
         {
         {
             PhysicalLocationsList = Array.Empty<string>();
             PhysicalLocationsList = Array.Empty<string>();
@@ -85,8 +87,6 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        private bool _requiresRefresh;
-
         public override bool RequiresRefresh()
         public override bool RequiresRefresh()
         {
         {
             var changed = base.RequiresRefresh() || _requiresRefresh;
             var changed = base.RequiresRefresh() || _requiresRefresh;
@@ -106,11 +106,11 @@ namespace MediaBrowser.Controller.Entities
             return changed;
             return changed;
         }
         }
 
 
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
             ClearCache();
             ClearCache();
 
 
-            var changed = base.BeforeMetadataRefresh(replaceAllMetdata) || _requiresRefresh;
+            var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh;
             _requiresRefresh = false;
             _requiresRefresh = false;
             return changed;
             return changed;
         }
         }

+ 2 - 2
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -208,9 +208,9 @@ namespace MediaBrowser.Controller.Entities.Audio
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             if (IsAccessedByName)
             if (IsAccessedByName)
             {
             {

+ 3 - 3
MediaBrowser.Controller/Entities/Audio/MusicGenre.cs

@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Entities.Audio
         public override bool IsDisplayedAsFolder => true;
         public override bool IsDisplayedAsFolder => true;
 
 
         /// <summary>
         /// <summary>
-        /// Returns the folder containing the item.
+        /// Gets the folder containing the item.
         /// If the item is a folder, it returns the folder itself.
         /// If the item is a folder, it returns the folder itself.
         /// </summary>
         /// </summary>
         /// <value>The containing folder path.</value>
         /// <value>The containing folder path.</value>
@@ -106,9 +106,9 @@ namespace MediaBrowser.Controller.Entities.Audio
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             var newPath = GetRebasedPath();
             var newPath = GetRebasedPath();
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))

+ 24 - 15
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -92,7 +92,8 @@ namespace MediaBrowser.Controller.Entities
         public const string ShortsFolderName = "shorts";
         public const string ShortsFolderName = "shorts";
         public const string FeaturettesFolderName = "featurettes";
         public const string FeaturettesFolderName = "featurettes";
 
 
-        public static readonly string[] AllExtrasTypesFolderNames = {
+        public static readonly string[] AllExtrasTypesFolderNames =
+        {
             ExtrasFolderName,
             ExtrasFolderName,
             BehindTheScenesFolderName,
             BehindTheScenesFolderName,
             DeletedScenesFolderName,
             DeletedScenesFolderName,
@@ -177,7 +178,7 @@ namespace MediaBrowser.Controller.Entities
         public virtual bool AlwaysScanInternalMetadataPath => false;
         public virtual bool AlwaysScanInternalMetadataPath => false;
 
 
         /// <summary>
         /// <summary>
-        /// Gets a value indicating whether this instance is in mixed folder.
+        /// Gets or sets a value indicating whether this instance is in mixed folder.
         /// </summary>
         /// </summary>
         /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
         [JsonIgnore]
         [JsonIgnore]
@@ -244,7 +245,7 @@ namespace MediaBrowser.Controller.Entities
         public ProgramAudio? Audio { get; set; }
         public ProgramAudio? Audio { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Return the id that should be used to key display prefs for this item.
+        /// Gets the id that should be used to key display prefs for this item.
         /// Default is based on the type for everything except actual generic folders.
         /// Default is based on the type for everything except actual generic folders.
         /// </summary>
         /// </summary>
         /// <value>The display prefs id.</value>
         /// <value>The display prefs id.</value>
@@ -280,7 +281,7 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Returns the folder containing the item.
+        /// Gets the folder containing the item.
         /// If the item is a folder, it returns the folder itself.
         /// If the item is a folder, it returns the folder itself.
         /// </summary>
         /// </summary>
         [JsonIgnore]
         [JsonIgnore]
@@ -305,8 +306,11 @@ namespace MediaBrowser.Controller.Entities
         public string ServiceName { get; set; }
         public string ServiceName { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// If this content came from an external service, the id of the content on that service.
+        /// Gets or sets the external id.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// If this content came from an external service, the id of the content on that service.
+        /// </remarks>
         [JsonIgnore]
         [JsonIgnore]
         public string ExternalId { get; set; }
         public string ExternalId { get; set; }
 
 
@@ -330,7 +334,7 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the type of the location.
+        /// Gets the type of the location.
         /// </summary>
         /// </summary>
         /// <value>The type of the location.</value>
         /// <value>The type of the location.</value>
         [JsonIgnore]
         [JsonIgnore]
@@ -449,8 +453,11 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// This is just a helper for convenience.
+        /// Gets the primary image path.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// This is just a helper for convenience.
+        /// </remarks>
         /// <value>The primary image path.</value>
         /// <value>The primary image path.</value>
         [JsonIgnore]
         [JsonIgnore]
         public string PrimaryImagePath => this.GetImagePath(ImageType.Primary);
         public string PrimaryImagePath => this.GetImagePath(ImageType.Primary);
@@ -541,7 +548,7 @@ namespace MediaBrowser.Controller.Entities
         public DateTime DateLastRefreshed { get; set; }
         public DateTime DateLastRefreshed { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// The logger.
+        /// Gets or sets the logger.
         /// </summary>
         /// </summary>
         public static ILogger<BaseItem> Logger { get; set; }
         public static ILogger<BaseItem> Logger { get; set; }
 
 
@@ -621,7 +628,7 @@ namespace MediaBrowser.Controller.Entities
         private Guid[] _themeVideoIds;
         private Guid[] _themeVideoIds;
 
 
         /// <summary>
         /// <summary>
-        /// Gets the name of the sort.
+        /// Gets or sets the name of the sort.
         /// </summary>
         /// </summary>
         /// <value>The name of the sort.</value>
         /// <value>The name of the sort.</value>
         [JsonIgnore]
         [JsonIgnore]
@@ -848,7 +855,7 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// When the item first debuted. For movies this could be premiere date, episodes would be first aired
+        /// Gets or sets the date that the item first debuted. For movies this could be premiere date, episodes would be first aired.
         /// </summary>
         /// </summary>
         /// <value>The premiere date.</value>
         /// <value>The premiere date.</value>
         [JsonIgnore]
         [JsonIgnore]
@@ -945,7 +952,7 @@ namespace MediaBrowser.Controller.Entities
         public int? ProductionYear { get; set; }
         public int? ProductionYear { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// If the item is part of a series, this is it's number in the series.
+        /// Gets or sets the index number. If the item is part of a series, this is it's number in the series.
         /// This could be episode number, album track number, etc.
         /// This could be episode number, album track number, etc.
         /// </summary>
         /// </summary>
         /// <value>The index number.</value>
         /// <value>The index number.</value>
@@ -953,7 +960,7 @@ namespace MediaBrowser.Controller.Entities
         public int? IndexNumber { get; set; }
         public int? IndexNumber { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// For an episode this could be the season number, or for a song this could be the disc number.
+        /// Gets or sets the parent index number. For an episode this could be the season number, or for a song this could be the disc number.
         /// </summary>
         /// </summary>
         /// <value>The parent index number.</value>
         /// <value>The parent index number.</value>
         [JsonIgnore]
         [JsonIgnore]
@@ -1017,9 +1024,9 @@ namespace MediaBrowser.Controller.Entities
             }
             }
 
 
             // if (!user.IsParentalScheduleAllowed())
             // if (!user.IsParentalScheduleAllowed())
-            //{
+            // {
             //    return PlayAccess.None;
             //    return PlayAccess.None;
-            //}
+            // }
 
 
             return PlayAccess.Full;
             return PlayAccess.Full;
         }
         }
@@ -2645,7 +2652,9 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true if changes were made.
         /// This is called before any metadata refresh and returns true if changes were made.
         /// </summary>
         /// </summary>
-        public virtual bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        /// <param name="replaceAllMetadata">Whether to replace all metadata.</param>
+        /// <returns>true if the item has change, else false.</returns>
+        public virtual bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
             _sortName = null;
             _sortName = null;
 
 

+ 38 - 28
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -29,30 +29,45 @@ namespace MediaBrowser.Controller.Entities
     public class CollectionFolder : Folder, ICollectionFolder
     public class CollectionFolder : Folder, ICollectionFolder
     {
     {
         private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
         private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
-        public static IXmlSerializer XmlSerializer { get; set; }
-
-        public static IServerApplicationHost ApplicationHost { get; set; }
+        private static readonly Dictionary<string, LibraryOptions> _libraryOptions = new Dictionary<string, LibraryOptions>();
+        private bool _requiresRefresh;
 
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CollectionFolder"/> class.
+        /// </summary>
         public CollectionFolder()
         public CollectionFolder()
         {
         {
             PhysicalLocationsList = Array.Empty<string>();
             PhysicalLocationsList = Array.Empty<string>();
             PhysicalFolderIds = Array.Empty<Guid>();
             PhysicalFolderIds = Array.Empty<Guid>();
         }
         }
 
 
+        public static IXmlSerializer XmlSerializer { get; set; }
+
+        public static IServerApplicationHost ApplicationHost { get; set; }
+
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsPlayedStatus => false;
         public override bool SupportsPlayedStatus => false;
 
 
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsInheritedParentImages => false;
         public override bool SupportsInheritedParentImages => false;
 
 
+        public string CollectionType { get; set; }
+
+        /// <summary>
+        /// Gets the item's children.
+        /// </summary>
+        /// <remarks>
+        /// Our children are actually just references to the ones in the physical root...
+        /// </remarks>
+        /// <value>The actual children.</value>
+        [JsonIgnore]
+        public override IEnumerable<BaseItem> Children => GetActualChildren();
+
         public override bool CanDelete()
         public override bool CanDelete()
         {
         {
             return false;
             return false;
         }
         }
 
 
-        public string CollectionType { get; set; }
-
-        private static readonly Dictionary<string, LibraryOptions> LibraryOptions = new Dictionary<string, LibraryOptions>();
         public LibraryOptions GetLibraryOptions()
         public LibraryOptions GetLibraryOptions()
         {
         {
             return GetLibraryOptions(Path);
             return GetLibraryOptions(Path);
@@ -106,12 +121,12 @@ namespace MediaBrowser.Controller.Entities
 
 
         public static LibraryOptions GetLibraryOptions(string path)
         public static LibraryOptions GetLibraryOptions(string path)
         {
         {
-            lock (LibraryOptions)
+            lock (_libraryOptions)
             {
             {
-                if (!LibraryOptions.TryGetValue(path, out var options))
+                if (!_libraryOptions.TryGetValue(path, out var options))
                 {
                 {
                     options = LoadLibraryOptions(path);
                     options = LoadLibraryOptions(path);
-                    LibraryOptions[path] = options;
+                    _libraryOptions[path] = options;
                 }
                 }
 
 
                 return options;
                 return options;
@@ -120,9 +135,9 @@ namespace MediaBrowser.Controller.Entities
 
 
         public static void SaveLibraryOptions(string path, LibraryOptions options)
         public static void SaveLibraryOptions(string path, LibraryOptions options)
         {
         {
-            lock (LibraryOptions)
+            lock (_libraryOptions)
             {
             {
-                LibraryOptions[path] = options;
+                _libraryOptions[path] = options;
 
 
                 var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
                 var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
                 foreach (var mediaPath in clone.PathInfos)
                 foreach (var mediaPath in clone.PathInfos)
@@ -139,15 +154,18 @@ namespace MediaBrowser.Controller.Entities
 
 
         public static void OnCollectionFolderChange()
         public static void OnCollectionFolderChange()
         {
         {
-            lock (LibraryOptions)
+            lock (_libraryOptions)
             {
             {
-                LibraryOptions.Clear();
+                _libraryOptions.Clear();
             }
             }
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Allow different display preferences for each collection folder.
+        /// Gets the display preferences id.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// Allow different display preferences for each collection folder.
+        /// </remarks>
         /// <value>The display prefs id.</value>
         /// <value>The display prefs id.</value>
         [JsonIgnore]
         [JsonIgnore]
         public override Guid DisplayPreferencesId => Id;
         public override Guid DisplayPreferencesId => Id;
@@ -155,21 +173,20 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         [JsonIgnore]
         public override string[] PhysicalLocations => PhysicalLocationsList;
         public override string[] PhysicalLocations => PhysicalLocationsList;
 
 
+        public string[] PhysicalLocationsList { get; set; }
+
+        public Guid[] PhysicalFolderIds { get; set; }
+
         public override bool IsSaveLocalMetadataEnabled()
         public override bool IsSaveLocalMetadataEnabled()
         {
         {
             return true;
             return true;
         }
         }
 
 
-        public string[] PhysicalLocationsList { get; set; }
-
-        public Guid[] PhysicalFolderIds { get; set; }
-
         protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
         protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
         {
         {
             return CreateResolveArgs(directoryService, true).FileSystemChildren;
             return CreateResolveArgs(directoryService, true).FileSystemChildren;
         }
         }
 
 
-        private bool _requiresRefresh;
         public override bool RequiresRefresh()
         public override bool RequiresRefresh()
         {
         {
             var changed = base.RequiresRefresh() || _requiresRefresh;
             var changed = base.RequiresRefresh() || _requiresRefresh;
@@ -201,9 +218,9 @@ namespace MediaBrowser.Controller.Entities
             return changed;
             return changed;
         }
         }
 
 
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var changed = base.BeforeMetadataRefresh(replaceAllMetdata) || _requiresRefresh;
+            var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh;
             _requiresRefresh = false;
             _requiresRefresh = false;
             return changed;
             return changed;
         }
         }
@@ -312,13 +329,6 @@ namespace MediaBrowser.Controller.Entities
             return Task.CompletedTask;
             return Task.CompletedTask;
         }
         }
 
 
-        /// <summary>
-        /// Our children are actually just references to the ones in the physical root...
-        /// </summary>
-        /// <value>The actual children.</value>
-        [JsonIgnore]
-        public override IEnumerable<BaseItem> Children => GetActualChildren();
-
         public IEnumerable<BaseItem> GetActualChildren()
         public IEnumerable<BaseItem> GetActualChildren()
         {
         {
             return GetPhysicalFolders(true).SelectMany(c => c.Children);
             return GetPhysicalFolders(true).SelectMany(c => c.Children);

+ 84 - 86
MediaBrowser.Controller/Entities/Folder.cs

@@ -37,6 +37,11 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class Folder : BaseItem
     public class Folder : BaseItem
     {
     {
+        public Folder()
+        {
+            LinkedChildren = Array.Empty<LinkedChild>();
+        }
+
         public static IUserViewManager UserViewManager { get; set; }
         public static IUserViewManager UserViewManager { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -50,11 +55,6 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         [JsonIgnore]
         public DateTime? DateLastMediaAdded { get; set; }
         public DateTime? DateLastMediaAdded { get; set; }
 
 
-        public Folder()
-        {
-            LinkedChildren = Array.Empty<LinkedChild>();
-        }
-
         [JsonIgnore]
         [JsonIgnore]
         public override bool SupportsThemeMedia => true;
         public override bool SupportsThemeMedia => true;
 
 
@@ -86,6 +86,85 @@ namespace MediaBrowser.Controller.Entities
         [JsonIgnore]
         [JsonIgnore]
         public virtual bool SupportsDateLastMediaAdded => false;
         public virtual bool SupportsDateLastMediaAdded => false;
 
 
+        [JsonIgnore]
+        public override string FileNameWithoutExtension
+        {
+            get
+            {
+                if (IsFileProtocol)
+                {
+                    return System.IO.Path.GetFileName(Path);
+                }
+
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Gets the actual children.
+        /// </summary>
+        /// <value>The actual children.</value>
+        [JsonIgnore]
+        public virtual IEnumerable<BaseItem> Children => LoadChildren();
+
+        /// <summary>
+        /// Gets thread-safe access to all recursive children of this folder - without regard to user.
+        /// </summary>
+        /// <value>The recursive children.</value>
+        [JsonIgnore]
+        public IEnumerable<BaseItem> RecursiveChildren => GetRecursiveChildren();
+
+        [JsonIgnore]
+        protected virtual bool SupportsShortcutChildren => false;
+
+        protected virtual bool FilterLinkedChildrenPerUser => false;
+
+        [JsonIgnore]
+        protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren;
+
+        [JsonIgnore]
+        public virtual bool SupportsUserDataFromChildren
+        {
+            get
+            {
+                // These are just far too slow.
+                if (this is ICollectionFolder)
+                {
+                    return false;
+                }
+
+                if (this is UserView)
+                {
+                    return false;
+                }
+
+                if (this is UserRootFolder)
+                {
+                    return false;
+                }
+
+                if (this is Channel)
+                {
+                    return false;
+                }
+
+                if (SourceType != SourceType.Library)
+                {
+                    return false;
+                }
+
+                if (this is IItemByName)
+                {
+                    if (this is not IHasDualAccess hasDualAccess || hasDualAccess.IsAccessedByName)
+                    {
+                        return false;
+                    }
+                }
+
+                return true;
+            }
+        }
+
         public override bool CanDelete()
         public override bool CanDelete()
         {
         {
             if (IsRoot)
             if (IsRoot)
@@ -108,20 +187,6 @@ namespace MediaBrowser.Controller.Entities
             return baseResult;
             return baseResult;
         }
         }
 
 
-        [JsonIgnore]
-        public override string FileNameWithoutExtension
-        {
-            get
-            {
-                if (IsFileProtocol)
-                {
-                    return System.IO.Path.GetFileName(Path);
-                }
-
-                return null;
-            }
-        }
-
         protected override bool IsAllowTagFilterEnforced()
         protected override bool IsAllowTagFilterEnforced()
         {
         {
             if (this is ICollectionFolder)
             if (this is ICollectionFolder)
@@ -137,9 +202,6 @@ namespace MediaBrowser.Controller.Entities
             return true;
             return true;
         }
         }
 
 
-        [JsonIgnore]
-        protected virtual bool SupportsShortcutChildren => false;
-
         /// <summary>
         /// <summary>
         /// Adds the child.
         /// Adds the child.
         /// </summary>
         /// </summary>
@@ -169,20 +231,6 @@ namespace MediaBrowser.Controller.Entities
             LibraryManager.CreateItem(item, this);
             LibraryManager.CreateItem(item, this);
         }
         }
 
 
-        /// <summary>
-        /// Gets the actual children.
-        /// </summary>
-        /// <value>The actual children.</value>
-        [JsonIgnore]
-        public virtual IEnumerable<BaseItem> Children => LoadChildren();
-
-        /// <summary>
-        /// thread-safe access to all recursive children of this folder - without regard to user.
-        /// </summary>
-        /// <value>The recursive children.</value>
-        [JsonIgnore]
-        public IEnumerable<BaseItem> RecursiveChildren => GetRecursiveChildren();
-
         public override bool IsVisible(User user)
         public override bool IsVisible(User user)
         {
         {
             if (this is ICollectionFolder && !(this is BasePluginFolder))
             if (this is ICollectionFolder && !(this is BasePluginFolder))
@@ -1428,8 +1476,6 @@ namespace MediaBrowser.Controller.Entities
             return list;
             return list;
         }
         }
 
 
-        protected virtual bool FilterLinkedChildrenPerUser => false;
-
         public bool ContainsLinkedChildByItemId(Guid itemId)
         public bool ContainsLinkedChildByItemId(Guid itemId)
         {
         {
             var linkedChildren = LinkedChildren;
             var linkedChildren = LinkedChildren;
@@ -1530,9 +1576,6 @@ namespace MediaBrowser.Controller.Entities
                 .Where(i => i.Item2 != null);
                 .Where(i => i.Item2 != null);
         }
         }
 
 
-        [JsonIgnore]
-        protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren;
-
         protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
         protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
         {
         {
             var changesFound = false;
             var changesFound = false;
@@ -1696,51 +1739,6 @@ namespace MediaBrowser.Controller.Entities
             return !IsPlayed(user);
             return !IsPlayed(user);
         }
         }
 
 
-        [JsonIgnore]
-        public virtual bool SupportsUserDataFromChildren
-        {
-            get
-            {
-                // These are just far too slow.
-                if (this is ICollectionFolder)
-                {
-                    return false;
-                }
-
-                if (this is UserView)
-                {
-                    return false;
-                }
-
-                if (this is UserRootFolder)
-                {
-                    return false;
-                }
-
-                if (this is Channel)
-                {
-                    return false;
-                }
-
-                if (SourceType != SourceType.Library)
-                {
-                    return false;
-                }
-
-                var iItemByName = this as IItemByName;
-                if (iItemByName != null)
-                {
-                    var hasDualAccess = this as IHasDualAccess;
-                    if (hasDualAccess == null || hasDualAccess.IsAccessedByName)
-                    {
-                        return false;
-                    }
-                }
-
-                return true;
-            }
-        }
-
         public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields)
         public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields)
         {
         {
             if (!SupportsUserDataFromChildren)
             if (!SupportsUserDataFromChildren)

+ 20 - 22
MediaBrowser.Controller/Entities/Genre.cs

@@ -16,6 +16,23 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class Genre : BaseItem, IItemByName
     public class Genre : BaseItem, IItemByName
     {
     {
+        /// <summary>
+        /// Gets the folder containing the item.
+        /// If the item is a folder, it returns the folder itself.
+        /// </summary>
+        /// <value>The containing folder path.</value>
+        [JsonIgnore]
+        public override string ContainingFolderPath => Path;
+
+        [JsonIgnore]
+        public override bool IsDisplayedAsFolder => true;
+
+        [JsonIgnore]
+        public override bool SupportsAncestors => false;
+
+        [JsonIgnore]
+        public override bool SupportsPeople => false;
+
         public override List<string> GetUserDataKeys()
         public override List<string> GetUserDataKeys()
         {
         {
             var list = base.GetUserDataKeys();
             var list = base.GetUserDataKeys();
@@ -34,20 +51,6 @@ namespace MediaBrowser.Controller.Entities
             return 1;
             return 1;
         }
         }
 
 
-        /// <summary>
-        /// Gets the folder containing the item.
-        /// If the item is a folder, it returns the folder itself.
-        /// </summary>
-        /// <value>The containing folder path.</value>
-        [JsonIgnore]
-        public override string ContainingFolderPath => Path;
-
-        [JsonIgnore]
-        public override bool IsDisplayedAsFolder => true;
-
-        [JsonIgnore]
-        public override bool SupportsAncestors => false;
-
         public override bool IsSaveLocalMetadataEnabled()
         public override bool IsSaveLocalMetadataEnabled()
         {
         {
             return true;
             return true;
@@ -72,9 +75,6 @@ namespace MediaBrowser.Controller.Entities
             return LibraryManager.GetItemList(query);
             return LibraryManager.GetItemList(query);
         }
         }
 
 
-        [JsonIgnore]
-        public override bool SupportsPeople => false;
-
         public static string GetPath(string name)
         public static string GetPath(string name)
         {
         {
             return GetPath(name, true);
             return GetPath(name, true);
@@ -107,12 +107,10 @@ namespace MediaBrowser.Controller.Entities
             return base.RequiresRefresh();
             return base.RequiresRefresh();
         }
         }
 
 
-        /// <summary>
-        /// This is called before any metadata refresh and returns true or false indicating if changes were made.
-        /// </summary>
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        /// <inheridoc />
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             var newPath = GetRebasedPath();
             var newPath = GetRebasedPath();
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))

+ 1 - 1
MediaBrowser.Controller/Entities/IHasSeries.cs

@@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Entities
     public interface IHasSeries
     public interface IHasSeries
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets the name of the series.
+        /// Gets or sets the name of the series.
         /// </summary>
         /// </summary>
         /// <value>The name of the series.</value>
         /// <value>The name of the series.</value>
         string SeriesName { get; set; }
         string SeriesName { get; set; }

+ 11 - 0
MediaBrowser.Controller/Entities/IHasShares.cs

@@ -0,0 +1,11 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Controller.Entities
+{
+    public interface IHasShares
+    {
+        Share[] Shares { get; set; }
+    }
+}

+ 41 - 44
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
 using System;
 using System;
@@ -20,9 +18,9 @@ namespace MediaBrowser.Controller.Entities
 
 
         public int? Limit { get; set; }
         public int? Limit { get; set; }
 
 
-        public User User { get; set; }
+        public User? User { get; set; }
 
 
-        public BaseItem SimilarTo { get; set; }
+        public BaseItem? SimilarTo { get; set; }
 
 
         public bool? IsFolder { get; set; }
         public bool? IsFolder { get; set; }
 
 
@@ -58,23 +56,23 @@ namespace MediaBrowser.Controller.Entities
 
 
         public bool? CollapseBoxSetItems { get; set; }
         public bool? CollapseBoxSetItems { get; set; }
 
 
-        public string NameStartsWithOrGreater { get; set; }
+        public string? NameStartsWithOrGreater { get; set; }
 
 
-        public string NameStartsWith { get; set; }
+        public string? NameStartsWith { get; set; }
 
 
-        public string NameLessThan { get; set; }
+        public string? NameLessThan { get; set; }
 
 
-        public string NameContains { get; set; }
+        public string? NameContains { get; set; }
 
 
-        public string MinSortName { get; set; }
+        public string? MinSortName { get; set; }
 
 
-        public string PresentationUniqueKey { get; set; }
+        public string? PresentationUniqueKey { get; set; }
 
 
-        public string Path { get; set; }
+        public string? Path { get; set; }
 
 
-        public string Name { get; set; }
+        public string? Name { get; set; }
 
 
-        public string Person { get; set; }
+        public string? Person { get; set; }
 
 
         public Guid[] PersonIds { get; set; }
         public Guid[] PersonIds { get; set; }
 
 
@@ -82,7 +80,7 @@ namespace MediaBrowser.Controller.Entities
 
 
         public Guid[] ExcludeItemIds { get; set; }
         public Guid[] ExcludeItemIds { get; set; }
 
 
-        public string AdjacentTo { get; set; }
+        public string? AdjacentTo { get; set; }
 
 
         public string[] PersonTypes { get; set; }
         public string[] PersonTypes { get; set; }
 
 
@@ -182,13 +180,13 @@ namespace MediaBrowser.Controller.Entities
 
 
         public Guid ParentId { get; set; }
         public Guid ParentId { get; set; }
 
 
-        public string ParentType { get; set; }
+        public string? ParentType { get; set; }
 
 
         public Guid[] AncestorIds { get; set; }
         public Guid[] AncestorIds { get; set; }
 
 
         public Guid[] TopParentIds { get; set; }
         public Guid[] TopParentIds { get; set; }
 
 
-        public BaseItem Parent
+        public BaseItem? Parent
         {
         {
             set
             set
             {
             {
@@ -213,9 +211,9 @@ namespace MediaBrowser.Controller.Entities
 
 
         public SeriesStatus[] SeriesStatuses { get; set; }
         public SeriesStatus[] SeriesStatuses { get; set; }
 
 
-        public string ExternalSeriesId { get; set; }
+        public string? ExternalSeriesId { get; set; }
 
 
-        public string ExternalId { get; set; }
+        public string? ExternalId { get; set; }
 
 
         public Guid[] AlbumIds { get; set; }
         public Guid[] AlbumIds { get; set; }
 
 
@@ -223,9 +221,9 @@ namespace MediaBrowser.Controller.Entities
 
 
         public Guid[] ExcludeArtistIds { get; set; }
         public Guid[] ExcludeArtistIds { get; set; }
 
 
-        public string AncestorWithPresentationUniqueKey { get; set; }
+        public string? AncestorWithPresentationUniqueKey { get; set; }
 
 
-        public string SeriesPresentationUniqueKey { get; set; }
+        public string? SeriesPresentationUniqueKey { get; set; }
 
 
         public bool GroupByPresentationUniqueKey { get; set; }
         public bool GroupByPresentationUniqueKey { get; set; }
 
 
@@ -235,7 +233,7 @@ namespace MediaBrowser.Controller.Entities
 
 
         public bool ForceDirect { get; set; }
         public bool ForceDirect { get; set; }
 
 
-        public Dictionary<string, string> ExcludeProviderIds { get; set; }
+        public Dictionary<string, string>? ExcludeProviderIds { get; set; }
 
 
         public bool EnableGroupByMetadataKey { get; set; }
         public bool EnableGroupByMetadataKey { get; set; }
 
 
@@ -253,13 +251,13 @@ namespace MediaBrowser.Controller.Entities
 
 
         public int MinSimilarityScore { get; set; }
         public int MinSimilarityScore { get; set; }
 
 
-        public string HasNoAudioTrackWithLanguage { get; set; }
+        public string? HasNoAudioTrackWithLanguage { get; set; }
 
 
-        public string HasNoInternalSubtitleTrackWithLanguage { get; set; }
+        public string? HasNoInternalSubtitleTrackWithLanguage { get; set; }
 
 
-        public string HasNoExternalSubtitleTrackWithLanguage { get; set; }
+        public string? HasNoExternalSubtitleTrackWithLanguage { get; set; }
 
 
-        public string HasNoSubtitleTrackWithLanguage { get; set; }
+        public string? HasNoSubtitleTrackWithLanguage { get; set; }
 
 
         public bool? IsDeadArtist { get; set; }
         public bool? IsDeadArtist { get; set; }
 
 
@@ -283,12 +281,10 @@ namespace MediaBrowser.Controller.Entities
             ExcludeInheritedTags = Array.Empty<string>();
             ExcludeInheritedTags = Array.Empty<string>();
             ExcludeItemIds = Array.Empty<Guid>();
             ExcludeItemIds = Array.Empty<Guid>();
             ExcludeItemTypes = Array.Empty<string>();
             ExcludeItemTypes = Array.Empty<string>();
-            ExcludeProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             ExcludeTags = Array.Empty<string>();
             ExcludeTags = Array.Empty<string>();
             GenreIds = Array.Empty<Guid>();
             GenreIds = Array.Empty<Guid>();
             Genres = Array.Empty<string>();
             Genres = Array.Empty<string>();
             GroupByPresentationUniqueKey = true;
             GroupByPresentationUniqueKey = true;
-            HasAnyProviderId = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             ImageTypes = Array.Empty<ImageType>();
             ImageTypes = Array.Empty<ImageType>();
             IncludeItemTypes = Array.Empty<string>();
             IncludeItemTypes = Array.Empty<string>();
             ItemIds = Array.Empty<Guid>();
             ItemIds = Array.Empty<Guid>();
@@ -309,32 +305,33 @@ namespace MediaBrowser.Controller.Entities
             Years = Array.Empty<int>();
             Years = Array.Empty<int>();
         }
         }
 
 
-        public InternalItemsQuery(User user)
+        public InternalItemsQuery(User? user)
             : this()
             : this()
         {
         {
-            SetUser(user);
+            if (user != null)
+            {
+                SetUser(user);
+            }
         }
         }
 
 
         public void SetUser(User user)
         public void SetUser(User user)
         {
         {
-            if (user != null)
-            {
-                MaxParentalRating = user.MaxParentalAgeRating;
+            MaxParentalRating = user.MaxParentalAgeRating;
 
 
-                if (MaxParentalRating.HasValue)
-                {
-                    BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
-                        .Where(i => i != UnratedItem.Other.ToString())
-                        .Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
-                }
+            if (MaxParentalRating.HasValue)
+            {
+                string other = UnratedItem.Other.ToString();
+                BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
+                    .Where(i => i != other)
+                    .Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
+            }
 
 
-                ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
+            ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
 
 
-                User = user;
-            }
+            User = user;
         }
         }
 
 
-        public Dictionary<string, string> HasAnyProviderId { get; set; }
+        public Dictionary<string, string>? HasAnyProviderId { get; set; }
 
 
         public Guid[] AlbumArtistIds { get; set; }
         public Guid[] AlbumArtistIds { get; set; }
 
 
@@ -356,8 +353,8 @@ namespace MediaBrowser.Controller.Entities
 
 
         public int? MinWidth { get; set; }
         public int? MinWidth { get; set; }
 
 
-        public string SearchTerm { get; set; }
+        public string? SearchTerm { get; set; }
 
 
-        public string SeriesTimerId { get; set; }
+        public string? SeriesTimerId { get; set; }
     }
     }
 }
 }

+ 2 - 2
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -144,9 +144,9 @@ namespace MediaBrowser.Controller.Entities.Movies
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             if (!ProductionYear.HasValue)
             if (!ProductionYear.HasValue)
             {
             {

+ 2 - 2
MediaBrowser.Controller/Entities/MusicVideo.cs

@@ -36,9 +36,9 @@ namespace MediaBrowser.Controller.Entities
             return info;
             return info;
         }
         }
 
 
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             if (!ProductionYear.HasValue)
             if (!ProductionYear.HasValue)
             {
             {

+ 6 - 3
MediaBrowser.Controller/Entities/Person.cs

@@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Returns the folder containing the item.
+        /// Gets the folder containing the item.
         /// If the item is a folder, it returns the folder itself.
         /// If the item is a folder, it returns the folder itself.
         /// </summary>
         /// </summary>
         /// <value>The containing folder path.</value>
         /// <value>The containing folder path.</value>
@@ -67,6 +67,9 @@ namespace MediaBrowser.Controller.Entities
             return true;
             return true;
         }
         }
 
 
+        /// <summary>
+        /// Gets a value indicating whether to enable alpha numeric sorting.
+        /// </summary>
         [JsonIgnore]
         [JsonIgnore]
         public override bool EnableAlphaNumericSorting => false;
         public override bool EnableAlphaNumericSorting => false;
 
 
@@ -126,9 +129,9 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             var newPath = GetRebasedPath();
             var newPath = GetRebasedPath();
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))

+ 0 - 5
MediaBrowser.Controller/Entities/Share.cs

@@ -4,11 +4,6 @@
 
 
 namespace MediaBrowser.Controller.Entities
 namespace MediaBrowser.Controller.Entities
 {
 {
-    public interface IHasShares
-    {
-        Share[] Shares { get; set; }
-    }
-
     public class Share
     public class Share
     {
     {
         public string UserId { get; set; }
         public string UserId { get; set; }

+ 3 - 3
MediaBrowser.Controller/Entities/Studio.cs

@@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Returns the folder containing the item.
+        /// Gets the folder containing the item.
         /// If the item is a folder, it returns the folder itself.
         /// If the item is a folder, it returns the folder itself.
         /// </summary>
         /// </summary>
         /// <value>The containing folder path.</value>
         /// <value>The containing folder path.</value>
@@ -105,9 +105,9 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             var newPath = GetRebasedPath();
             var newPath = GetRebasedPath();
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))

+ 7 - 6
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Entities.TV
         public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
         public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the season in which it aired.
+        /// Gets or sets the season in which it aired.
         /// </summary>
         /// </summary>
         /// <value>The aired season.</value>
         /// <value>The aired season.</value>
         public int? AirsBeforeSeasonNumber { get; set; }
         public int? AirsBeforeSeasonNumber { get; set; }
@@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities.TV
         public int? AirsBeforeEpisodeNumber { get; set; }
         public int? AirsBeforeEpisodeNumber { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// This is the ending episode number for double episodes.
+        /// Gets or sets the ending episode number for double episodes.
         /// </summary>
         /// </summary>
         /// <value>The index number.</value>
         /// <value>The index number.</value>
         public int? IndexNumberEnd { get; set; }
         public int? IndexNumberEnd { get; set; }
@@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.Entities.TV
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// This Episode's Series Instance.
+        /// Gets the Episode's Series Instance.
         /// </summary>
         /// </summary>
         /// <value>The series.</value>
         /// <value>The series.</value>
         [JsonIgnore]
         [JsonIgnore]
@@ -261,6 +261,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
 
         [JsonIgnore]
         [JsonIgnore]
         public Guid SeasonId { get; set; }
         public Guid SeasonId { get; set; }
+
         [JsonIgnore]
         [JsonIgnore]
         public Guid SeriesId { get; set; }
         public Guid SeriesId { get; set; }
 
 
@@ -318,9 +319,9 @@ namespace MediaBrowser.Controller.Entities.TV
             return id;
             return id;
         }
         }
 
 
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             if (!IsLocked)
             if (!IsLocked)
             {
             {
@@ -328,7 +329,7 @@ namespace MediaBrowser.Controller.Entities.TV
                 {
                 {
                     try
                     try
                     {
                     {
-                        if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetdata))
+                        if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetadata))
                         {
                         {
                             hasChanges = true;
                             hasChanges = true;
                         }
                         }

+ 3 - 3
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.Entities.TV
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// This Episode's Series Instance.
+        /// Gets this Episode's Series Instance.
         /// </summary>
         /// </summary>
         /// <value>The series.</value>
         /// <value>The series.</value>
         [JsonIgnore]
         [JsonIgnore]
@@ -242,9 +242,9 @@ namespace MediaBrowser.Controller.Entities.TV
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// This is called before any metadata refresh and returns true or false indicating if changes were made.
         /// </summary>
         /// </summary>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
             if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
             {
             {

+ 4 - 1
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -59,8 +59,11 @@ namespace MediaBrowser.Controller.Entities.TV
         public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
         public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// airdate, dvd or absolute.
+        /// Gets or sets the display order.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// Valid options are airdate, dvd or absolute.
+        /// </remarks>
         public string DisplayOrder { get; set; }
         public string DisplayOrder { get; set; }
 
 
         /// <summary>
         /// <summary>

+ 2 - 2
MediaBrowser.Controller/Entities/Trailer.cs

@@ -45,9 +45,9 @@ namespace MediaBrowser.Controller.Entities
             return info;
             return info;
         }
         }
 
 
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             if (!ProductionYear.HasValue)
             if (!ProductionYear.HasValue)
             {
             {

+ 1 - 1
MediaBrowser.Controller/Entities/UserItemData.cs

@@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Entities
         public const double MinLikeValue = 6.5;
         public const double MinLikeValue = 6.5;
 
 
         /// <summary>
         /// <summary>
-        /// This is an interpreted property to indicate likes or dislikes
+        /// Gets or sets a value indicating whether the item is liked or not.
         /// This should never be serialized.
         /// This should never be serialized.
         /// </summary>
         /// </summary>
         /// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value>
         /// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value>

+ 3 - 2
MediaBrowser.Controller/Entities/UserRootFolder.cs

@@ -23,6 +23,7 @@ namespace MediaBrowser.Controller.Entities
     {
     {
         private List<Guid> _childrenIds = null;
         private List<Guid> _childrenIds = null;
         private readonly object _childIdsLock = new object();
         private readonly object _childIdsLock = new object();
+
         protected override List<BaseItem> LoadChildren()
         protected override List<BaseItem> LoadChildren()
         {
         {
             lock (_childIdsLock)
             lock (_childIdsLock)
@@ -87,10 +88,10 @@ namespace MediaBrowser.Controller.Entities
             return list;
             return list;
         }
         }
 
 
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
             ClearCache();
             ClearCache();
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase))
             if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase))
             {
             {

+ 13 - 7
MediaBrowser.Controller/Entities/UserView.cs

@@ -15,13 +15,19 @@ namespace MediaBrowser.Controller.Entities
 {
 {
     public class UserView : Folder, IHasCollectionType
     public class UserView : Folder, IHasCollectionType
     {
     {
-        /// <inheritdoc />
+        /// <summary>
+        /// Gets or sets the view type.
+        /// </summary>
         public string ViewType { get; set; }
         public string ViewType { get; set; }
 
 
-        /// <inheritdoc />
+        /// <summary>
+        /// Gets or sets the display parent id.
+        /// </summary>
         public new Guid DisplayParentId { get; set; }
         public new Guid DisplayParentId { get; set; }
 
 
-        /// <inheritdoc />
+        /// <summary>
+        /// Gets or sets the user id.
+        /// </summary>
         public Guid? UserId { get; set; }
         public Guid? UserId { get; set; }
 
 
         public static ITVSeriesManager TVSeriesManager;
         public static ITVSeriesManager TVSeriesManager;
@@ -110,10 +116,10 @@ namespace MediaBrowser.Controller.Entities
             return GetChildren(user, false);
             return GetChildren(user, false);
         }
         }
 
 
-        private static string[] UserSpecificViewTypes = new string[]
-            {
-                Model.Entities.CollectionType.Playlists
-            };
+        private static readonly string[] UserSpecificViewTypes = new string[]
+        {
+            Model.Entities.CollectionType.Playlists
+        };
 
 
         public static bool IsUserSpecific(Folder folder)
         public static bool IsUserSpecific(Folder folder)
         {
         {

+ 6 - 4
MediaBrowser.Controller/Entities/Year.cs

@@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Returns the folder containing the item.
+        /// Gets the folder containing the item.
         /// If the item is a folder, it returns the folder itself.
         /// If the item is a folder, it returns the folder itself.
         /// </summary>
         /// </summary>
         /// <value>The containing folder path.</value>
         /// <value>The containing folder path.</value>
@@ -112,11 +112,13 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// This is called before any metadata refresh and returns true or false indicating if changes were made.
+        /// This is called before any metadata refresh and returns true if changes were made.
         /// </summary>
         /// </summary>
-        public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
+        /// <param name="replaceAllMetadata">Whether to replace all metadata.</param>
+        /// <returns>true if the item has change, else false.</returns>
+        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
         {
         {
-            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
+            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 
 
             var newPath = GetRebasedPath();
             var newPath = GetRebasedPath();
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))
             if (!string.Equals(Path, newPath, StringComparison.Ordinal))

+ 1 - 1
MediaBrowser.Controller/Extensions/StringExtensions.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Extensions
                 {
                 {
                     // will throw if input contains invalid unicode chars
                     // will throw if input contains invalid unicode chars
                     // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
                     // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
-                    text = Regex.Replace(text, "([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])", "");
+                    text = Regex.Replace(text, "([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])", string.Empty);
                     return Normalize(text, form, false);
                     return Normalize(text, form, false);
                 }
                 }
             }
             }

+ 6 - 0
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -43,6 +43,12 @@ namespace MediaBrowser.Controller.Library
         /// <summary>
         /// <summary>
         /// Resolves a set of files into a list of BaseItem.
         /// Resolves a set of files into a list of BaseItem.
         /// </summary>
         /// </summary>
+        /// <param name="files">The list of tiles.</param>
+        /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
+        /// <param name="parent">The parent folder.</param>
+        /// <param name="libraryOptions">The library options.</param>
+        /// <param name="collectionType">The collection type.</param>
+        /// <returns>The items resolved from the paths.</returns>
         IEnumerable<BaseItem> ResolvePaths(
         IEnumerable<BaseItem> ResolvePaths(
             IEnumerable<FileSystemMetadata> files,
             IEnumerable<FileSystemMetadata> files,
             IDirectoryService directoryService,
             IDirectoryService directoryService,

+ 1 - 1
MediaBrowser.Controller/LiveTv/LiveTvChannel.cs

@@ -148,7 +148,7 @@ namespace MediaBrowser.Controller.LiveTv
         public bool IsNews { get; set; }
         public bool IsNews { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets a value indicating whether this instance is kids.
+        /// Gets a value indicating whether this instance is kids.
         /// </summary>
         /// </summary>
         /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
         [JsonIgnore]
         [JsonIgnore]

+ 1 - 1
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

@@ -71,7 +71,7 @@ namespace MediaBrowser.Controller.LiveTv
         public override SourceType SourceType => SourceType.LiveTV;
         public override SourceType SourceType => SourceType.LiveTV;
 
 
         /// <summary>
         /// <summary>
-        /// The start date of the program, in UTC.
+        /// Gets or sets start date of the program, in UTC.
         /// </summary>
         /// </summary>
         [JsonIgnore]
         [JsonIgnore]
         public DateTime StartDate { get; set; }
         public DateTime StartDate { get; set; }

+ 7 - 8
MediaBrowser.Controller/LiveTv/TimerInfo.cs

@@ -28,18 +28,17 @@ namespace MediaBrowser.Controller.LiveTv
         public string[] Tags { get; set; }
         public string[] Tags { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Id of the recording.
+        /// Gets or sets the id of the recording.
         /// </summary>
         /// </summary>
         public string Id { get; set; }
         public string Id { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the series timer identifier.
         /// Gets or sets the series timer identifier.
         /// </summary>
         /// </summary>
-        /// <value>The series timer identifier.</value>
         public string SeriesTimerId { get; set; }
         public string SeriesTimerId { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// ChannelId of the recording.
+        /// Gets or sets the channelId of the recording.
         /// </summary>
         /// </summary>
         public string ChannelId { get; set; }
         public string ChannelId { get; set; }
 
 
@@ -52,24 +51,24 @@ namespace MediaBrowser.Controller.LiveTv
         public string ShowId { get; set; }
         public string ShowId { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Name of the recording.
+        /// Gets or sets the name of the recording.
         /// </summary>
         /// </summary>
         public string Name { get; set; }
         public string Name { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Description of the recording.
+        /// Gets or sets the description of the recording.
         /// </summary>
         /// </summary>
         public string Overview { get; set; }
         public string Overview { get; set; }
 
 
         public string SeriesId { get; set; }
         public string SeriesId { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// The start date of the recording, in UTC.
+        /// Gets or sets the start date of the recording, in UTC.
         /// </summary>
         /// </summary>
         public DateTime StartDate { get; set; }
         public DateTime StartDate { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// The end date of the recording, in UTC.
+        /// Gets or sets the end date of the recording, in UTC.
         /// </summary>
         /// </summary>
         public DateTime EndDate { get; set; }
         public DateTime EndDate { get; set; }
 
 
@@ -133,7 +132,7 @@ namespace MediaBrowser.Controller.LiveTv
         public bool IsSeries { get; set; }
         public bool IsSeries { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets a value indicating whether this instance is live.
+        /// Gets a value indicating whether this instance is live.
         /// </summary>
         /// </summary>
         /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
         [JsonIgnore]
         [JsonIgnore]

+ 4 - 3
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -596,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding
                     && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
                     && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
                     && isNvdecDecoder)
                     && isNvdecDecoder)
                 {
                 {
-                    arg.Append("-hwaccel_output_format cuda -autorotate 0 ");
+                    // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
+                    arg.Append("-hwaccel_output_format cuda -extra_hw_frames 3 -autorotate 0 ");
                 }
                 }
 
 
                 if (state.IsVideoRequest
                 if (state.IsVideoRequest
@@ -1070,7 +1071,6 @@ namespace MediaBrowser.Controller.MediaEncoding
             else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
             else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
                      || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
                      || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
             {
             {
-                // following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead.
                 switch (encodingOptions.EncoderPreset)
                 switch (encodingOptions.EncoderPreset)
                 {
                 {
                     case "veryslow":
                     case "veryslow":
@@ -1251,7 +1251,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
             }
 
 
             if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
             if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
-                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
+                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
             {
             {
                 profile = "constrained_baseline";
                 profile = "constrained_baseline";
             }
             }
@@ -2933,6 +2933,7 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
             return threads;
             return threads;
         }
         }
+
 #nullable disable
 #nullable disable
         public void TryStreamCopy(EncodingJobInfo state)
         public void TryStreamCopy(EncodingJobInfo state)
         {
         {

+ 5 - 26
MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs

@@ -430,7 +430,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Predicts the audio sample rate that will be in the output stream.
+        /// Gets the target video level.
         /// </summary>
         /// </summary>
         public double? TargetVideoLevel
         public double? TargetVideoLevel
         {
         {
@@ -453,7 +453,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Predicts the audio sample rate that will be in the output stream.
+        /// Gets the target video bit depth.
         /// </summary>
         /// </summary>
         public int? TargetVideoBitDepth
         public int? TargetVideoBitDepth
         {
         {
@@ -488,7 +488,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Predicts the audio sample rate that will be in the output stream.
+        /// Gets the target framerate.
         /// </summary>
         /// </summary>
         public float? TargetFramerate
         public float? TargetFramerate
         {
         {
@@ -520,7 +520,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Predicts the audio sample rate that will be in the output stream.
+        /// Gets the target packet length.
         /// </summary>
         /// </summary>
         public int? TargetPacketLength
         public int? TargetPacketLength
         {
         {
@@ -536,7 +536,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Predicts the audio sample rate that will be in the output stream.
+        /// Gets the target video profile.
         /// </summary>
         /// </summary>
         public string TargetVideoProfile
         public string TargetVideoProfile
         {
         {
@@ -700,25 +700,4 @@ namespace MediaBrowser.Controller.MediaEncoding
             Progress.Report(percentComplete.Value);
             Progress.Report(percentComplete.Value);
         }
         }
     }
     }
-
-    /// <summary>
-    /// Enum TranscodingJobType.
-    /// </summary>
-    public enum TranscodingJobType
-    {
-        /// <summary>
-        /// The progressive.
-        /// </summary>
-        Progressive,
-
-        /// <summary>
-        /// The HLS.
-        /// </summary>
-        Hls,
-
-        /// <summary>
-        /// The dash.
-        /// </summary>
-        Dash
-    }
 }
 }

+ 0 - 49
MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs

@@ -4,59 +4,10 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Linq;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 
 
 namespace MediaBrowser.Controller.MediaEncoding
 namespace MediaBrowser.Controller.MediaEncoding
 {
 {
-    public class EncodingJobOptions : BaseEncodingJobOptions
-    {
-        public string OutputDirectory { get; set; }
-
-        public string ItemId { get; set; }
-
-        public string TempDirectory { get; set; }
-
-        public bool ReadInputAtNativeFramerate { get; set; }
-
-        /// <summary>
-        /// Gets a value indicating whether this instance has fixed resolution.
-        /// </summary>
-        /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
-        public bool HasFixedResolution => Width.HasValue || Height.HasValue;
-
-        public DeviceProfile DeviceProfile { get; set; }
-
-        public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile)
-        {
-            Container = info.Container;
-            StartTimeTicks = info.StartPositionTicks;
-            MaxWidth = info.MaxWidth;
-            MaxHeight = info.MaxHeight;
-            MaxFramerate = info.MaxFramerate;
-            Id = info.ItemId;
-            MediaSourceId = info.MediaSourceId;
-            AudioCodec = info.TargetAudioCodec.FirstOrDefault();
-            MaxAudioChannels = info.GlobalMaxAudioChannels;
-            AudioBitRate = info.AudioBitrate;
-            AudioSampleRate = info.TargetAudioSampleRate;
-            DeviceProfile = deviceProfile;
-            VideoCodec = info.TargetVideoCodec.FirstOrDefault();
-            VideoBitRate = info.VideoBitrate;
-            AudioStreamIndex = info.AudioStreamIndex;
-            SubtitleMethod = info.SubtitleDeliveryMethod;
-            Context = info.Context;
-            TranscodingMaxAudioChannels = info.TranscodingMaxAudioChannels;
-
-            if (info.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External)
-            {
-                SubtitleStreamIndex = info.SubtitleStreamIndex;
-            }
-
-            StreamOptions = info.StreamOptions;
-        }
-    }
-
     // For now until api and media encoding layers are unified
     // For now until api and media encoding layers are unified
     public class BaseEncodingJobOptions
     public class BaseEncodingJobOptions
     {
     {

+ 1 - 1
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.MediaEncoding
     public interface IMediaEncoder : ITranscoderSupport
     public interface IMediaEncoder : ITranscoderSupport
     {
     {
         /// <summary>
         /// <summary>
-        /// The location of the discovered FFmpeg tool.
+        /// Gets location of the discovered FFmpeg tool.
         /// </summary>
         /// </summary>
         FFmpegLocation EncoderLocation { get; }
         FFmpegLocation EncoderLocation { get; }
 
 

+ 23 - 0
MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs

@@ -0,0 +1,23 @@
+namespace MediaBrowser.Controller.MediaEncoding
+{
+    /// <summary>
+    /// Enum TranscodingJobType.
+    /// </summary>
+    public enum TranscodingJobType
+    {
+        /// <summary>
+        /// The progressive.
+        /// </summary>
+        Progressive,
+
+        /// <summary>
+        /// The HLS.
+        /// </summary>
+        Hls,
+
+        /// <summary>
+        /// The dash.
+        /// </summary>
+        Dash
+    }
+}

+ 8 - 8
MediaBrowser.Controller/Playlists/Playlist.cs

@@ -22,14 +22,14 @@ namespace MediaBrowser.Controller.Playlists
 {
 {
     public class Playlist : Folder, IHasShares
     public class Playlist : Folder, IHasShares
     {
     {
-        public static string[] SupportedExtensions =
-            {
-                ".m3u",
-                ".m3u8",
-                ".pls",
-                ".wpl",
-                ".zpl"
-            };
+        public static readonly IReadOnlyList<string> SupportedExtensions = new[]
+        {
+            ".m3u",
+            ".m3u8",
+            ".pls",
+            ".wpl",
+            ".zpl"
+        };
 
 
         public Guid OwnerUserId { get; set; }
         public Guid OwnerUserId { get; set; }
 
 

+ 9 - 0
MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs

@@ -0,0 +1,9 @@
+namespace MediaBrowser.Controller.Plugins
+{
+    /// <summary>
+    /// Indicates that a <see cref="IServerEntryPoint"/> should be invoked as a pre-startup task.
+    /// </summary>
+    public interface IRunBeforeStartup
+    {
+    }
+}

+ 1 - 7
MediaBrowser.Controller/Plugins/IServerEntryPoint.cs

@@ -14,13 +14,7 @@ namespace MediaBrowser.Controller.Plugins
         /// <summary>
         /// <summary>
         /// Run the initialization for this module. This method is invoked at application start.
         /// Run the initialization for this module. This method is invoked at application start.
         /// </summary>
         /// </summary>
+        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
         Task RunAsync();
         Task RunAsync();
     }
     }
-
-    /// <summary>
-    /// Indicates that a <see cref="IServerEntryPoint"/> should be invoked as a pre-startup task.
-    /// </summary>
-    public interface IRunBeforeStartup
-    {
-    }
 }
 }

+ 1 - 1
MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs

@@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether all existing data should be overwritten with new data from providers
         /// Gets or sets a value indicating whether all existing data should be overwritten with new data from providers
-        /// when paired with MetadataRefreshMode=FullRefresh
+        /// when paired with MetadataRefreshMode=FullRefresh.
         /// </summary>
         /// </summary>
         public bool ReplaceAllMetadata { get; set; }
         public bool ReplaceAllMetadata { get; set; }
 
 

+ 0 - 22
MediaBrowser.Controller/Sync/IHasDynamicAccess.cs

@@ -1,22 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Sync;
-
-namespace MediaBrowser.Controller.Sync
-{
-    public interface IHasDynamicAccess
-    {
-        /// <summary>
-        /// Gets the synced file information.
-        /// </summary>
-        /// <param name="id">The identifier.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task&lt;SyncedFileInfo&gt;.</returns>
-        Task<SyncedFileInfo> GetSyncedFileInfo(string id, SyncTarget target, CancellationToken cancellationToken);
-    }
-}

+ 0 - 9
MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs

@@ -1,9 +0,0 @@
-namespace MediaBrowser.Controller.Sync
-{
-    /// <summary>
-    /// A marker interface.
-    /// </summary>
-    public interface IRemoteSyncProvider
-    {
-    }
-}

+ 0 - 32
MediaBrowser.Controller/Sync/IServerSyncProvider.cs

@@ -1,32 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Sync;
-
-namespace MediaBrowser.Controller.Sync
-{
-    public interface IServerSyncProvider : ISyncProvider
-    {
-        /// <summary>
-        /// Transfers the file.
-        /// </summary>
-        Task<SyncedFileInfo> SendFile(SyncJob syncJob, string originalMediaPath, Stream inputStream, bool isMedia, string[] outputPathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
-
-        Task<QueryResult<FileSystemMetadata>> GetFiles(string[] directoryPathParts, SyncTarget target, CancellationToken cancellationToken);
-    }
-
-    public interface ISupportsDirectCopy
-    {
-        /// <summary>
-        /// Sends the file.
-        /// </summary>
-        Task<SyncedFileInfo> SendFile(SyncJob syncJob, string originalMediaPath, string inputPath, bool isMedia, string[] outputPathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
-    }
-}

+ 0 - 31
MediaBrowser.Controller/Sync/ISyncProvider.cs

@@ -1,31 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Model.Sync;
-
-namespace MediaBrowser.Controller.Sync
-{
-    public interface ISyncProvider
-    {
-        /// <summary>
-        /// Gets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        string Name { get; }
-
-        /// <summary>
-        /// Gets the synchronize targets.
-        /// </summary>
-        /// <param name="userId">The user identifier.</param>
-        /// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
-        List<SyncTarget> GetSyncTargets(string userId);
-
-        /// <summary>
-        /// Gets all synchronize targets.
-        /// </summary>
-        /// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
-        List<SyncTarget> GetAllSyncTargets();
-    }
-}

+ 0 - 43
MediaBrowser.Controller/Sync/SyncedFileInfo.cs

@@ -1,43 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Model.MediaInfo;
-
-namespace MediaBrowser.Controller.Sync
-{
-    public class SyncedFileInfo
-    {
-        public SyncedFileInfo()
-        {
-            RequiredHttpHeaders = new Dictionary<string, string>();
-        }
-
-        /// <summary>
-        /// Gets or sets the path.
-        /// </summary>
-        /// <value>The path.</value>
-        public string Path { get; set; }
-
-        public string[] PathParts { get; set; }
-
-        /// <summary>
-        /// Gets or sets the protocol.
-        /// </summary>
-        /// <value>The protocol.</value>
-        public MediaProtocol Protocol { get; set; }
-
-        /// <summary>
-        /// Gets or sets the required HTTP headers.
-        /// </summary>
-        /// <value>The required HTTP headers.</value>
-        public Dictionary<string, string> RequiredHttpHeaders { get; set; }
-
-        /// <summary>
-        /// Gets or sets the identifier.
-        /// </summary>
-        /// <value>The identifier.</value>
-        public string Id { get; set; }
-    }
-}

+ 10 - 0
MediaBrowser.Controller/TV/ITVSeriesManager.cs

@@ -6,16 +6,26 @@ using MediaBrowser.Model.Querying;
 
 
 namespace MediaBrowser.Controller.TV
 namespace MediaBrowser.Controller.TV
 {
 {
+    /// <summary>
+    /// The TV Series manager.
+    /// </summary>
     public interface ITVSeriesManager
     public interface ITVSeriesManager
     {
     {
         /// <summary>
         /// <summary>
         /// Gets the next up.
         /// Gets the next up.
         /// </summary>
         /// </summary>
+        /// <param name="query">The next up query.</param>
+        /// <param name="options">The dto options.</param>
+        /// <returns>The next up items.</returns>
         QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options);
         QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options);
 
 
         /// <summary>
         /// <summary>
         /// Gets the next up.
         /// Gets the next up.
         /// </summary>
         /// </summary>
+        /// <param name="request">The next up request.</param>
+        /// <param name="parentsFolders">The list of parent folders.</param>
+        /// <param name="options">The dto options.</param>
+        /// <returns>The next up items.</returns>
         QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options);
         QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options);
     }
     }
 }
 }

+ 14 - 3
MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs

@@ -466,7 +466,7 @@ namespace MediaBrowser.LocalMetadata.Images
             return added;
             return added;
         }
         }
 
 
-        private bool AddImage(IEnumerable<FileSystemMetadata> files, List<LocalImageInfo> images, string name, ImageType type)
+        private bool AddImage(List<FileSystemMetadata> files, List<LocalImageInfo> images, string name, ImageType type)
         {
         {
             var image = GetImage(files, name);
             var image = GetImage(files, name);
 
 
@@ -484,9 +484,20 @@ namespace MediaBrowser.LocalMetadata.Images
             return false;
             return false;
         }
         }
 
 
-        private FileSystemMetadata? GetImage(IEnumerable<FileSystemMetadata> files, string name)
+        private static FileSystemMetadata? GetImage(IReadOnlyList<FileSystemMetadata> files, string name)
         {
         {
-            return files.FirstOrDefault(i => !i.IsDirectory && string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase) && i.Length > 0);
+            for (var i = 0; i < files.Count; i++)
+            {
+                var file = files[i];
+                if (!file.IsDirectory
+                    && file.Length > 0
+                    && Path.GetFileNameWithoutExtension(file.FullName.AsSpan()).Equals(name, StringComparison.OrdinalIgnoreCase))
+                {
+                    return file;
+                }
+            }
+
+            return null;
         }
         }
     }
     }
 }
 }

+ 67 - 68
MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs

@@ -15,17 +15,6 @@ namespace MediaBrowser.Providers.MediaInfo
     {
     {
         private readonly ILocalizationManager _localization;
         private readonly ILocalizationManager _localization;
 
 
-        private static readonly HashSet<string> SubtitleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
-        {
-            ".srt",
-            ".ssa",
-            ".ass",
-            ".sub",
-            ".smi",
-            ".sami",
-            ".vtt"
-        };
-
         public SubtitleResolver(ILocalizationManager localization)
         public SubtitleResolver(ILocalizationManager localization)
         {
         {
             _localization = localization;
             _localization = localization;
@@ -88,80 +77,65 @@ namespace MediaBrowser.Providers.MediaInfo
             return list;
             return list;
         }
         }
 
 
-        private void AddExternalSubtitleStreams(
-            List<MediaStream> streams,
-            string folder,
-            string videoPath,
-            int startIndex,
-            IDirectoryService directoryService,
-            bool clearCache)
-        {
-            var files = directoryService.GetFilePaths(folder, clearCache).OrderBy(i => i).ToArray();
-
-            AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
-        }
-
         public void AddExternalSubtitleStreams(
         public void AddExternalSubtitleStreams(
             List<MediaStream> streams,
             List<MediaStream> streams,
             string videoPath,
             string videoPath,
             int startIndex,
             int startIndex,
             string[] files)
             string[] files)
         {
         {
-            var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoPath);
-            videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension);
+            var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath);
 
 
             foreach (var fullName in files)
             foreach (var fullName in files)
             {
             {
-                var extension = Path.GetExtension(fullName);
-
-                if (!SubtitleExtensions.Contains(extension))
+                var extension = Path.GetExtension(fullName.AsSpan());
+                if (!IsSubtitleExtension(extension))
                 {
                 {
                     continue;
                     continue;
                 }
                 }
 
 
-                var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
-                fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fileNameWithoutExtension);
+                var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName);
 
 
-                if (!string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) &&
-                    !fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
-                {
-                    continue;
-                }
-
-                var codec = Path.GetExtension(fullName).ToLowerInvariant().TrimStart('.');
-
-                if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase))
-                {
-                    codec = "srt";
-                }
+                MediaStream mediaStream;
 
 
-                // If the subtitle file matches the video file name
-                if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+                // The subtitle filename must either be equal to the video filename or start with the video filename followed by a dot
+                if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    streams.Add(new MediaStream
+                    mediaStream = new MediaStream
                     {
                     {
                         Index = startIndex++,
                         Index = startIndex++,
                         Type = MediaStreamType.Subtitle,
                         Type = MediaStreamType.Subtitle,
                         IsExternal = true,
                         IsExternal = true,
-                        Path = fullName,
-                        Codec = codec
-                    });
+                        Path = fullName
+                    };
                 }
                 }
-                else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
+                else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length
+                         && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.'
+                         && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    var isForced = fullName.IndexOf(".forced.", StringComparison.OrdinalIgnoreCase) != -1 ||
-                        fullName.IndexOf(".foreign.", StringComparison.OrdinalIgnoreCase) != -1;
+                    var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase)
+                                   || fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase);
 
 
-                    var isDefault = fullName.IndexOf(".default.", StringComparison.OrdinalIgnoreCase) != -1;
+                    var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase);
 
 
                     // Support xbmc naming conventions - 300.spanish.srt
                     // Support xbmc naming conventions - 300.spanish.srt
-                    var language = fileNameWithoutExtension
-                        .Replace(".forced", string.Empty, StringComparison.OrdinalIgnoreCase)
-                        .Replace(".foreign", string.Empty, StringComparison.OrdinalIgnoreCase)
-                        .Replace(".default", string.Empty, StringComparison.OrdinalIgnoreCase)
-                        .Split('.')
-                        .LastOrDefault();
+                    var languageSpan = fileNameWithoutExtension;
+                    while (languageSpan.Length > 0)
+                    {
+                        var lastDot = languageSpan.LastIndexOf('.');
+                        var currentSlice = languageSpan[lastDot..];
+                        if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase)
+                            || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase)
+                            || currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase))
+                        {
+                            languageSpan = languageSpan[..lastDot];
+                            continue;
+                        }
+
+                        languageSpan = languageSpan[(lastDot + 1)..];
+                        break;
+                    }
 
 
+                    var language = languageSpan.ToString();
                     // Try to translate to three character code
                     // Try to translate to three character code
                     // Be flexible and check against both the full and three character versions
                     // Be flexible and check against both the full and three character versions
                     var culture = _localization.FindLanguageInfo(language);
                     var culture = _localization.FindLanguageInfo(language);
@@ -171,33 +145,58 @@ namespace MediaBrowser.Providers.MediaInfo
                         language = culture.ThreeLetterISOLanguageName;
                         language = culture.ThreeLetterISOLanguageName;
                     }
                     }
 
 
-                    streams.Add(new MediaStream
+                    mediaStream = new MediaStream
                     {
                     {
                         Index = startIndex++,
                         Index = startIndex++,
                         Type = MediaStreamType.Subtitle,
                         Type = MediaStreamType.Subtitle,
                         IsExternal = true,
                         IsExternal = true,
                         Path = fullName,
                         Path = fullName,
-                        Codec = codec,
                         Language = language,
                         Language = language,
                         IsForced = isForced,
                         IsForced = isForced,
                         IsDefault = isDefault
                         IsDefault = isDefault
-                    });
+                    };
+                }
+                else
+                {
+                    continue;
                 }
                 }
+
+                mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant();
+
+                streams.Add(mediaStream);
             }
             }
         }
         }
 
 
-        private string NormalizeFilenameForSubtitleComparison(string filename)
+        private static bool IsSubtitleExtension(ReadOnlySpan<char> extension)
+        {
+            return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase)
+                   || extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase)
+                   || extension.Equals(".ass", StringComparison.OrdinalIgnoreCase)
+                   || extension.Equals(".sub", StringComparison.OrdinalIgnoreCase)
+                   || extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase)
+                   || extension.Equals(".smi", StringComparison.OrdinalIgnoreCase)
+                   || extension.Equals(".sami", StringComparison.OrdinalIgnoreCase);
+        }
+
+        private static ReadOnlySpan<char> NormalizeFilenameForSubtitleComparison(string filename)
         {
         {
             // Try to account for sloppy file naming
             // Try to account for sloppy file naming
             filename = filename.Replace("_", string.Empty, StringComparison.Ordinal);
             filename = filename.Replace("_", string.Empty, StringComparison.Ordinal);
             filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal);
             filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal);
+            return Path.GetFileNameWithoutExtension(filename.AsSpan());
+        }
 
 
-            // can't normalize this due to languages such as pt-br
-            // filename = filename.Replace("-", string.Empty);
-
-            // filename = filename.Replace(".", string.Empty);
+        private void AddExternalSubtitleStreams(
+            List<MediaStream> streams,
+            string folder,
+            string videoPath,
+            int startIndex,
+            IDirectoryService directoryService,
+            bool clearCache)
+        {
+            var files = directoryService.GetFilePaths(folder, clearCache).OrderBy(i => i).ToArray();
 
 
-            return filename;
+            AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
         }
         }
     }
     }
 }
 }

+ 37 - 45
MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs

@@ -69,58 +69,52 @@ namespace MediaBrowser.Providers.Music
 
 
         private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
         private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
         {
         {
-            using (var oReader = new StreamReader(stream, Encoding.UTF8))
+            using var oReader = new StreamReader(stream, Encoding.UTF8);
+            var settings = new XmlReaderSettings()
             {
             {
-                var settings = new XmlReaderSettings()
-                {
-                    ValidationType = ValidationType.None,
-                    CheckCharacters = false,
-                    IgnoreProcessingInstructions = true,
-                    IgnoreComments = true
-                };
+                ValidationType = ValidationType.None,
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true
+            };
 
 
-                using (var reader = XmlReader.Create(oReader, settings))
-                {
-                    reader.MoveToContent();
-                    reader.Read();
+            using var reader = XmlReader.Create(oReader, settings);
+            reader.MoveToContent();
+            reader.Read();
 
 
-                    // Loop through each element
-                    while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+            // Loop through each element
+            while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
                     {
                     {
-                        if (reader.NodeType == XmlNodeType.Element)
+                        case "artist-list":
                         {
                         {
-                            switch (reader.Name)
+                            if (reader.IsEmptyElement)
                             {
                             {
-                                case "artist-list":
-                                    {
-                                        if (reader.IsEmptyElement)
-                                        {
-                                            reader.Read();
-                                            continue;
-                                        }
-
-                                        using (var subReader = reader.ReadSubtree())
-                                        {
-                                            return ParseArtistList(subReader).ToList();
-                                        }
-                                    }
-
-                                default:
-                                    {
-                                        reader.Skip();
-                                        break;
-                                    }
+                                reader.Read();
+                                continue;
                             }
                             }
+
+                            using var subReader = reader.ReadSubtree();
+                            return ParseArtistList(subReader).ToList();
                         }
                         }
-                        else
+
+                        default:
                         {
                         {
-                            reader.Read();
+                            reader.Skip();
+                            break;
                         }
                         }
                     }
                     }
-
-                    return Enumerable.Empty<RemoteSearchResult>();
+                }
+                else
+                {
+                    reader.Read();
                 }
                 }
             }
             }
+
+            return Enumerable.Empty<RemoteSearchResult>();
         }
         }
 
 
         private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader)
         private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader)
@@ -145,13 +139,11 @@ namespace MediaBrowser.Providers.Music
 
 
                                 var mbzId = reader.GetAttribute("id");
                                 var mbzId = reader.GetAttribute("id");
 
 
-                                using (var subReader = reader.ReadSubtree())
+                                using var subReader = reader.ReadSubtree();
+                                var artist = ParseArtist(subReader, mbzId);
+                                if (artist != null)
                                 {
                                 {
-                                    var artist = ParseArtist(subReader, mbzId);
-                                    if (artist != null)
-                                    {
-                                        yield return artist;
-                                    }
+                                    yield return artist;
                                 }
                                 }
 
 
                                 break;
                                 break;

+ 76 - 94
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs

@@ -128,53 +128,49 @@ namespace MediaBrowser.Providers.Music
 
 
         private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
         private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
         {
         {
-            using (var oReader = new StreamReader(stream, Encoding.UTF8))
+            using var oReader = new StreamReader(stream, Encoding.UTF8);
+            var settings = new XmlReaderSettings()
+            {
+                ValidationType = ValidationType.None,
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true
+            };
+
+            using var reader = XmlReader.Create(oReader, settings);
+            var results = ReleaseResult.Parse(reader);
+
+            return results.Select(i =>
             {
             {
-                var settings = new XmlReaderSettings()
+                var result = new RemoteSearchResult
                 {
                 {
-                    ValidationType = ValidationType.None,
-                    CheckCharacters = false,
-                    IgnoreProcessingInstructions = true,
-                    IgnoreComments = true
+                    Name = i.Title,
+                    ProductionYear = i.Year
                 };
                 };
 
 
-                using (var reader = XmlReader.Create(oReader, settings))
+                if (i.Artists.Count > 0)
                 {
                 {
-                    var results = ReleaseResult.Parse(reader);
-
-                    return results.Select(i =>
+                    result.AlbumArtist = new RemoteSearchResult
                     {
                     {
-                        var result = new RemoteSearchResult
-                        {
-                            Name = i.Title,
-                            ProductionYear = i.Year
-                        };
-
-                        if (i.Artists.Count > 0)
-                        {
-                            result.AlbumArtist = new RemoteSearchResult
-                            {
-                                SearchProviderName = Name,
-                                Name = i.Artists[0].Item1
-                            };
+                        SearchProviderName = Name,
+                        Name = i.Artists[0].Item1
+                    };
 
 
-                            result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2);
-                        }
-
-                        if (!string.IsNullOrWhiteSpace(i.ReleaseId))
-                        {
-                            result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId);
-                        }
+                    result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2);
+                }
 
 
-                        if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId))
-                        {
-                            result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId);
-                        }
+                if (!string.IsNullOrWhiteSpace(i.ReleaseId))
+                {
+                    result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId);
+                }
 
 
-                        return result;
-                    });
+                if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId))
+                {
+                    result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId);
                 }
                 }
-            }
+
+                return result;
+            });
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -339,10 +335,8 @@ namespace MediaBrowser.Providers.Music
                                         continue;
                                         continue;
                                     }
                                     }
 
 
-                                    using (var subReader = reader.ReadSubtree())
-                                    {
-                                        return ParseReleaseList(subReader).ToList();
-                                    }
+                                    using var subReader = reader.ReadSubtree();
+                                    return ParseReleaseList(subReader).ToList();
                                 }
                                 }
 
 
                             default:
                             default:
@@ -383,13 +377,11 @@ namespace MediaBrowser.Providers.Music
 
 
                                     var releaseId = reader.GetAttribute("id");
                                     var releaseId = reader.GetAttribute("id");
 
 
-                                    using (var subReader = reader.ReadSubtree())
+                                    using var subReader = reader.ReadSubtree();
+                                    var release = ParseRelease(subReader, releaseId);
+                                    if (release != null)
                                     {
                                     {
-                                        var release = ParseRelease(subReader, releaseId);
-                                        if (release != null)
-                                        {
-                                            yield return release;
-                                        }
+                                        yield return release;
                                     }
                                     }
 
 
                                     break;
                                     break;
@@ -460,14 +452,12 @@ namespace MediaBrowser.Providers.Music
 
 
                             case "artist-credit":
                             case "artist-credit":
                                 {
                                 {
-                                    using (var subReader = reader.ReadSubtree())
-                                    {
-                                        var artist = ParseArtistCredit(subReader);
+                                    using var subReader = reader.ReadSubtree();
+                                    var artist = ParseArtistCredit(subReader);
 
 
-                                        if (!string.IsNullOrEmpty(artist.Item1))
-                                        {
-                                            result.Artists.Add(artist);
-                                        }
+                                    if (!string.IsNullOrEmpty(artist.Item1))
+                                    {
+                                        result.Artists.Add(artist);
                                     }
                                     }
 
 
                                     break;
                                     break;
@@ -505,12 +495,10 @@ namespace MediaBrowser.Providers.Music
                     switch (reader.Name)
                     switch (reader.Name)
                     {
                     {
                         case "name-credit":
                         case "name-credit":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    return ParseArtistNameCredit(subReader);
-                                }
-                            }
+                        {
+                            using var subReader = reader.ReadSubtree();
+                            return ParseArtistNameCredit(subReader);
+                        }
 
 
                         default:
                         default:
                             {
                             {
@@ -545,10 +533,8 @@ namespace MediaBrowser.Providers.Music
                         case "artist":
                         case "artist":
                             {
                             {
                                 var id = reader.GetAttribute("id");
                                 var id = reader.GetAttribute("id");
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    return ParseArtistArtistCredit(subReader, id);
-                                }
+                                using var subReader = reader.ReadSubtree();
+                                return ParseArtistArtistCredit(subReader, id);
                             }
                             }
 
 
                         default:
                         default:
@@ -647,47 +633,43 @@ namespace MediaBrowser.Providers.Music
                 IgnoreComments = true
                 IgnoreComments = true
             };
             };
 
 
-            using (var reader = XmlReader.Create(oReader, settings))
-            {
-                reader.MoveToContent();
-                reader.Read();
+            using var reader = XmlReader.Create(oReader, settings);
+            reader.MoveToContent();
+            reader.Read();
 
 
-                // Loop through each element
-                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+            // Loop through each element
+            while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+            {
+                if (reader.NodeType == XmlNodeType.Element)
                 {
                 {
-                    if (reader.NodeType == XmlNodeType.Element)
+                    switch (reader.Name)
                     {
                     {
-                        switch (reader.Name)
+                        case "release-group-list":
                         {
                         {
-                            case "release-group-list":
+                            if (reader.IsEmptyElement)
                             {
                             {
-                                if (reader.IsEmptyElement)
-                                {
-                                    reader.Read();
-                                    continue;
-                                }
-
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    return GetFirstReleaseGroupId(subReader);
-                                }
+                                reader.Read();
+                                continue;
                             }
                             }
 
 
-                            default:
-                            {
-                                reader.Skip();
-                                break;
-                            }
+                            using var subReader = reader.ReadSubtree();
+                            return GetFirstReleaseGroupId(subReader);
+                        }
+
+                        default:
+                        {
+                            reader.Skip();
+                            break;
                         }
                         }
-                    }
-                    else
-                    {
-                        reader.Read();
                     }
                     }
                 }
                 }
-
-                return null;
+                else
+                {
+                    reader.Read();
+                }
             }
             }
+
+            return null;
         }
         }
 
 
         private string GetFirstReleaseGroupId(XmlReader reader)
         private string GetFirstReleaseGroupId(XmlReader reader)

+ 2 - 3
MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs

@@ -1,6 +1,5 @@
 #pragma warning disable CS1591
 #pragma warning disable CS1591
 
 
-using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
@@ -55,14 +54,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
                 return Enumerable.Empty<RemoteImageInfo>();
                 return Enumerable.Empty<RemoteImageInfo>();
             }
             }
 
 
-            var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false);
+            var language = item.GetPreferredMetadataLanguage();
+            var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), language, cancellationToken).ConfigureAwait(false);
             if (personResult?.Images?.Profiles == null)
             if (personResult?.Images?.Profiles == null)
             {
             {
                 return Enumerable.Empty<RemoteImageInfo>();
                 return Enumerable.Empty<RemoteImageInfo>();
             }
             }
 
 
             var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count];
             var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count];
-            var language = item.GetPreferredMetadataLanguage();
 
 
             for (var i = 0; i < personResult.Images.Profiles.Count; i++)
             for (var i = 0; i < personResult.Images.Profiles.Count; i++)
             {
             {

+ 2 - 3
MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs

@@ -3,7 +3,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
-using System.Linq;
 using System.Net.Http;
 using System.Net.Http;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -32,7 +31,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
         {
         {
             if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId))
             if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId))
             {
             {
-                var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false);
+                var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
 
 
                 if (personResult != null)
                 if (personResult != null)
                 {
                 {
@@ -96,7 +95,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
 
 
             if (personTmdbId > 0)
             if (personTmdbId > 0)
             {
             {
-                var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
+                var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false);
 
 
                 result.HasMetadata = true;
                 result.HasMetadata = true;
 
 

+ 4 - 2
MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs

@@ -276,11 +276,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
         /// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id.
         /// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id.
         /// </summary>
         /// </summary>
         /// <param name="personTmdbId">The person's TMDb id.</param>
         /// <param name="personTmdbId">The person's TMDb id.</param>
+        /// <param name="language">The episode's language.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>The TMDb person information or null if not found.</returns>
         /// <returns>The TMDb person information or null if not found.</returns>
-        public async Task<Person> GetPersonAsync(int personTmdbId, CancellationToken cancellationToken)
+        public async Task<Person> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken)
         {
         {
-            var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}";
+            var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
             if (_memoryCache.TryGetValue(key, out Person person))
             if (_memoryCache.TryGetValue(key, out Person person))
             {
             {
                 return person;
                 return person;
@@ -290,6 +291,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
 
 
             person = await _tmDbClient.GetPersonAsync(
             person = await _tmDbClient.GetPersonAsync(
                 personTmdbId,
                 personTmdbId,
+                TmdbUtils.NormalizeLanguage(language),
                 PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds,
                 PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds,
                 cancellationToken).ConfigureAwait(false);
                 cancellationToken).ConfigureAwait(false);
 
 

+ 6 - 0
MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs

@@ -148,6 +148,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
 
 
             if (parts.Length == 2)
             if (parts.Length == 2)
             {
             {
+                // TMDB doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without country code
+                if (string.Equals(parts[1], "CH", StringComparison.OrdinalIgnoreCase))
+                {
+                    return parts[0];
+                }
+
                 language = parts[0] + "-" + parts[1].ToUpperInvariant();
                 language = parts[0] + "-" + parts[1].ToUpperInvariant();
             }
             }
 
 

+ 9 - 13
MediaBrowser.Providers/Studios/StudiosImageProvider.cs

@@ -172,23 +172,19 @@ namespace MediaBrowser.Providers.Studios
 
 
         public IEnumerable<string> GetAvailableImages(string file)
         public IEnumerable<string> GetAvailableImages(string file)
         {
         {
-            using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
+            using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
+            using var reader = new StreamReader(fileStream);
+            var lines = new List<string>();
+
+            foreach (var line in reader.ReadAllLines())
             {
             {
-                using (var reader = new StreamReader(fileStream))
+                if (!string.IsNullOrWhiteSpace(line))
                 {
                 {
-                    var lines = new List<string>();
-
-                    foreach (var line in reader.ReadAllLines())
-                    {
-                        if (!string.IsNullOrWhiteSpace(line))
-                        {
-                            lines.Add(line);
-                        }
-                    }
-
-                    return lines;
+                    lines.Add(line);
                 }
                 }
             }
             }
+
+            return lines;
         }
         }
     }
     }
 }
 }

+ 33 - 37
MediaBrowser.Providers/Subtitles/SubtitleManager.cs

@@ -187,48 +187,46 @@ namespace MediaBrowser.Providers.Subtitles
         {
         {
             var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia;
             var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia;
 
 
-            using (var stream = response.Stream)
-            using (var memoryStream = new MemoryStream())
-            {
-                await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
-                memoryStream.Position = 0;
+            using var stream = response.Stream;
+            using var memoryStream = new MemoryStream();
+            await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
+            memoryStream.Position = 0;
 
 
-                var savePaths = new List<string>();
-                var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant();
+            var savePaths = new List<string>();
+            var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant();
 
 
-                if (response.IsForced)
-                {
-                    saveFileName += ".forced";
-                }
+            if (response.IsForced)
+            {
+                saveFileName += ".forced";
+            }
 
 
-                saveFileName += "." + response.Format.ToLowerInvariant();
+            saveFileName += "." + response.Format.ToLowerInvariant();
 
 
-                if (saveInMediaFolder)
+            if (saveInMediaFolder)
+            {
+                var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName));
+                // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path.");
+                if (mediaFolderPath.StartsWith(video.ContainingFolderPath, StringComparison.Ordinal))
                 {
                 {
-                    var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName));
-                    // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path.");
-                    if (mediaFolderPath.StartsWith(video.ContainingFolderPath, StringComparison.Ordinal))
-                    {
-                        savePaths.Add(mediaFolderPath);
-                    }
+                    savePaths.Add(mediaFolderPath);
                 }
                 }
+            }
 
 
-                var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName));
+            var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName));
 
 
-                // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path.");
-                if (internalPath.StartsWith(video.GetInternalMetadataPath(), StringComparison.Ordinal))
-                {
-                    savePaths.Add(internalPath);
-                }
+            // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path.");
+            if (internalPath.StartsWith(video.GetInternalMetadataPath(), StringComparison.Ordinal))
+            {
+                savePaths.Add(internalPath);
+            }
 
 
-                if (savePaths.Count > 0)
-                {
-                    await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false);
-                }
-                else
-                {
-                    _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid.");
-                }
+            if (savePaths.Count > 0)
+            {
+                await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false);
+            }
+            else
+            {
+                _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid.");
             }
             }
         }
         }
 
 
@@ -247,10 +245,8 @@ namespace MediaBrowser.Providers.Subtitles
                     Directory.CreateDirectory(Path.GetDirectoryName(savePath));
                     Directory.CreateDirectory(Path.GetDirectoryName(savePath));
 
 
                     // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
                     // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
-                    using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true))
-                    {
-                        await stream.CopyToAsync(fs).ConfigureAwait(false);
-                    }
+                    using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true);
+                    await stream.CopyToAsync(fs).ConfigureAwait(false);
 
 
                     return;
                     return;
                 }
                 }

+ 1 - 1
deployment/Dockerfile.debian.amd64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.debian.arm64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.debian.armhf

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.linux.amd64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.linux.amd64-musl

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.linux.arm64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.linux.armhf

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.macos

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.portable

@@ -15,7 +15,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.ubuntu.amd64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.ubuntu.arm64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 
 # Install dotnet repository
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

Деякі файли не було показано, через те що забагато файлів було змінено