Kaynağa Gözat

Merge branch 'master' into authenticationdb-efcore

# Conflicts:
#	Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
#	Emby.Server.Implementations/Session/SessionManager.cs
#	Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
Patrick Barron 4 yıl önce
ebeveyn
işleme
ae878fa051
100 değiştirilmiş dosya ile 286 ekleme ve 482 silme
  1. 2 0
      .github/workflows/automation.yml
  2. 1 1
      Emby.Dlna/DlnaManager.cs
  3. 1 1
      Emby.Dlna/Service/BaseControlHandler.cs
  4. 1 1
      Emby.Naming/Audio/AudioFileParser.cs
  5. 1 1
      Emby.Naming/Video/VideoResolver.cs
  6. 1 1
      Emby.Server.Implementations/Channels/ChannelManager.cs
  7. 3 1
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  8. 1 1
      Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
  9. 1 1
      Emby.Server.Implementations/IO/ManagedFileSystem.cs
  10. 1 0
      Emby.Server.Implementations/Library/LibraryManager.cs
  11. 1 1
      Emby.Server.Implementations/Library/LiveStreamHelper.cs
  12. 1 1
      Emby.Server.Implementations/Library/MediaSourceManager.cs
  13. 1 1
      Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  14. 1 1
      Emby.Server.Implementations/Library/SearchEngine.cs
  15. 2 2
      Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
  16. 1 1
      Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
  17. 1 1
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  18. 2 1
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  19. 1 0
      Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
  20. 2 2
      Emby.Server.Implementations/Localization/LocalizationManager.cs
  21. 2 2
      Emby.Server.Implementations/Plugins/PluginManager.cs
  22. 54 123
      Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
  23. 1 1
      Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  24. 3 2
      Emby.Server.Implementations/Session/SessionManager.cs
  25. 2 1
      Emby.Server.Implementations/Sorting/StudioComparer.cs
  26. 1 1
      Emby.Server.Implementations/Updates/InstallationManager.cs
  27. 1 1
      Jellyfin.Api/BaseJellyfinApiController.cs
  28. 1 1
      Jellyfin.Api/Controllers/ConfigurationController.cs
  29. 1 1
      Jellyfin.Api/Controllers/PluginsController.cs
  30. 23 59
      Jellyfin.Api/Controllers/QuickConnectController.cs
  31. 1 1
      Jellyfin.Api/Controllers/TvShowsController.cs
  32. 1 1
      Jellyfin.Api/Controllers/UserLibraryController.cs
  33. 1 1
      Jellyfin.Api/Extensions/DtoExtensions.cs
  34. 1 1
      Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
  35. 1 1
      Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs
  36. 2 2
      Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs
  37. 2 5
      Jellyfin.Drawing.Skia/SkiaEncoder.cs
  38. 1 1
      Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
  39. 1 1
      Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
  40. 1 1
      Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
  41. 1 1
      Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
  42. 1 1
      Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs
  43. 1 1
      Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
  44. 16 0
      Jellyfin.sln
  45. 1 1
      MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
  46. 1 1
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  47. 1 1
      MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
  48. 2 1
      MediaBrowser.Controller/Entities/BaseItem.cs
  49. 1 1
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  50. 1 1
      MediaBrowser.Controller/Entities/Extensions.cs
  51. 1 1
      MediaBrowser.Controller/Entities/Genre.cs
  52. 1 1
      MediaBrowser.Controller/Entities/Person.cs
  53. 1 1
      MediaBrowser.Controller/Entities/Studio.cs
  54. 0 73
      MediaBrowser.Controller/Extensions/StringExtensions.cs
  55. 1 0
      MediaBrowser.Controller/Library/NameExtensions.cs
  56. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  57. 4 62
      MediaBrowser.Controller/QuickConnect/IQuickConnect.cs
  58. 2 3
      MediaBrowser.Controller/Session/ISessionManager.cs
  59. 2 3
      MediaBrowser.Controller/Sorting/SortExtensions.cs
  60. 1 1
      MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
  61. 1 1
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  62. 1 1
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
  63. 7 4
      MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs
  64. 1 0
      MediaBrowser.Model/Entities/VirtualFolderInfo.cs
  65. 2 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  66. 20 12
      MediaBrowser.Model/QuickConnect/QuickConnectResult.cs
  67. 0 23
      MediaBrowser.Model/QuickConnect/QuickConnectState.cs
  68. 1 1
      MediaBrowser.Providers/Manager/ProviderUtils.cs
  69. 1 1
      MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
  70. 1 1
      MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
  71. 1 1
      MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
  72. 1 1
      MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
  73. 2 12
      MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
  74. 4 2
      MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs
  75. 4 2
      MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs
  76. 2 2
      MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
  77. 2 2
      MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
  78. 1 1
      MediaBrowser.Providers/Properties/AssemblyInfo.cs
  79. 1 1
      MediaBrowser.Providers/Studios/StudiosImageProvider.cs
  80. 1 1
      debian/rules
  81. 11 4
      src/Jellyfin.Extensions/AlphanumericComparator.cs
  82. 1 1
      src/Jellyfin.Extensions/CopyToExtensions.cs
  83. 2 2
      src/Jellyfin.Extensions/EnumerableExtensions.cs
  84. 30 0
      src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
  85. 2 2
      src/Jellyfin.Extensions/Json/Converters/JsonBoolNumberConverter.cs
  86. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs
  87. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs
  88. 2 2
      src/Jellyfin.Extensions/Json/Converters/JsonDateTimeConverter.cs
  89. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs
  90. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs
  91. 3 7
      src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs
  92. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs
  93. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs
  94. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverterFactory.cs
  95. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs
  96. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs
  97. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs
  98. 1 1
      src/Jellyfin.Extensions/Json/Converters/JsonVersionConverter.cs
  99. 2 2
      src/Jellyfin.Extensions/Json/JsonDefaults.cs
  100. 1 1
      src/Jellyfin.Extensions/ShuffleExtensions.cs

+ 2 - 0
.github/workflows/automation.yml

@@ -11,6 +11,7 @@ jobs:
   label:
     name: Labeling
     runs-on: ubuntu-latest
+    if: ${{ github.repository == 'jellyfin/jellyfin' }}
     steps:
       - name: Apply label
         uses: eps1lon/actions-label-merge-conflict@v2.0.1
@@ -22,6 +23,7 @@ jobs:
   project:
     name: Project board
     runs-on: ubuntu-latest
+    if: ${{ github.repository == 'jellyfin/jellyfin' }}
     steps:
       - name: Remove from 'Current Release' project
         uses: alex-page/github-project-automation-plus@v0.7.1

+ 1 - 1
Emby.Dlna/DlnaManager.cs

@@ -14,9 +14,9 @@ using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 using Emby.Dlna.Profiles;
 using Emby.Dlna.Server;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;

+ 1 - 1
Emby.Dlna/Service/BaseControlHandler.cs

@@ -6,9 +6,9 @@ using System.IO;
 using System.Text;
 using System.Threading.Tasks;
 using System.Xml;
+using Diacritics.Extensions;
 using Emby.Dlna.Didl;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Extensions;
 using Microsoft.Extensions.Logging;
 
 namespace Emby.Dlna.Service

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

@@ -1,7 +1,7 @@
 using System;
 using System.IO;
 using Emby.Naming.Common;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 
 namespace Emby.Naming.Audio
 {

+ 1 - 1
Emby.Naming/Video/VideoResolver.cs

@@ -2,7 +2,7 @@ using System;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using Emby.Naming.Common;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 
 namespace Emby.Naming.Video
 {

+ 1 - 1
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;

+ 3 - 1
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -11,10 +11,12 @@ using System.Linq;
 using System.Text;
 using System.Text.Json;
 using System.Threading;
+using Diacritics.Extensions;
 using Emby.Server.Implementations.Playlists;
 using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;

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

@@ -7,7 +7,7 @@ using System.Text;
 using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Session;

+ 1 - 1
Emby.Server.Implementations/IO/ManagedFileSystem.cs

@@ -6,8 +6,8 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
+using Jellyfin.Extensions;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.System;
 using Microsoft.Extensions.Logging;

+ 1 - 0
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -21,6 +21,7 @@ using Emby.Server.Implementations.Playlists;
 using Emby.Server.Implementations.ScheduledTasks;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller;

+ 1 - 1
Emby.Server.Implementations/Library/LiveStreamHelper.cs

@@ -12,7 +12,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;

+ 1 - 1
Emby.Server.Implementations/Library/MediaSourceManager.cs

@@ -15,7 +15,7 @@ using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;

+ 1 - 1
Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -6,7 +6,7 @@ using System.IO;
 using System.Linq;
 using System.Text.RegularExpressions;
 using Emby.Naming.Video;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;

+ 1 - 1
Emby.Server.Implementations/Library/SearchEngine.cs

@@ -5,12 +5,12 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Diacritics.Extensions;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Search;

+ 2 - 2
Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs

@@ -11,9 +11,9 @@ using System.Text;
 using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Extensions;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;

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

@@ -7,7 +7,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text.Json;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using Microsoft.Extensions.Logging;
 
 namespace Emby.Server.Implementations.LiveTv.EmbyTV

+ 1 - 1
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -15,7 +15,7 @@ using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.Cryptography;

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

@@ -12,8 +12,9 @@ using System.Net.Http;
 using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Extensions;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;

+ 1 - 0
Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs

@@ -10,6 +10,7 @@ using System.Net.Http;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;

+ 2 - 2
Emby.Server.Implementations/Localization/LocalizationManager.cs

@@ -8,8 +8,8 @@ using System.IO;
 using System.Reflection;
 using System.Text.Json;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;

+ 2 - 2
Emby.Server.Implementations/Plugins/PluginManager.cs

@@ -10,8 +10,8 @@ using System.Text.Json;
 using System.Threading.Tasks;
 using MediaBrowser.Common;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json;
+using Jellyfin.Extensions.Json.Converters;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Model.Configuration;

+ 54 - 123
Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs

@@ -1,15 +1,12 @@
-#nullable disable
-
 using System;
 using System.Collections.Concurrent;
-using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Security.Cryptography;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.QuickConnect;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.QuickConnect;
@@ -22,9 +19,18 @@ namespace Emby.Server.Implementations.QuickConnect
     /// </summary>
     public class QuickConnectManager : IQuickConnect, IDisposable
     {
-        private readonly RNGCryptoServiceProvider _rng = new ();
-        private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new ();
-        private readonly ConcurrentDictionary<string, (string Token, Guid UserId)> _quickConnectTokens = new ();
+        /// <summary>
+        /// The length of user facing codes.
+        /// </summary>
+        private const int CodeLength = 6;
+
+        /// <summary>
+        /// The time (in minutes) that the quick connect token is valid.
+        /// </summary>
+        private const int Timeout = 10;
+
+        private readonly RNGCryptoServiceProvider _rng = new();
+        private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new();
 
         private readonly IServerConfigurationManager _config;
         private readonly ILogger<QuickConnectManager> _logger;
@@ -34,80 +40,42 @@ namespace Emby.Server.Implementations.QuickConnect
         /// Initializes a new instance of the <see cref="QuickConnectManager"/> class.
         /// Should only be called at server startup when a singleton is created.
         /// </summary>
-        /// <param name="config">The server configuration manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="sessionManager">The session manager.</param>
-        public QuickConnectManager(IServerConfigurationManager config, ILogger<QuickConnectManager> logger, ISessionManager sessionManager)
+        /// <param name="config">Configuration.</param>
+        /// <param name="logger">Logger.</param>
+        /// <param name="sessionManager">Session Manager.</param>
+        public QuickConnectManager(
+            IServerConfigurationManager config,
+            ILogger<QuickConnectManager> logger,
+            ISessionManager sessionManager)
         {
             _config = config;
             _logger = logger;
             _sessionManager = sessionManager;
-
-            ReloadConfiguration();
         }
 
-        /// <inheritdoc/>
-        public int CodeLength { get; set; } = 6;
+        /// <inheritdoc />
+        public bool IsEnabled => _config.Configuration.QuickConnectAvailable;
 
-        /// <inheritdoc/>
-        public string TokenName { get; set; } = "QuickConnect";
-
-        /// <inheritdoc/>
-        public QuickConnectState State { get; private set; } = QuickConnectState.Unavailable;
-
-        /// <inheritdoc/>
-        public int Timeout { get; set; } = 5;
-
-        private DateTime DateActivated { get; set; }
-
-        /// <inheritdoc/>
-        public void AssertActive()
+        /// <summary>
+        /// Assert that quick connect is currently active and throws an exception if it is not.
+        /// </summary>
+        private void AssertActive()
         {
-            if (State != QuickConnectState.Active)
+            if (!IsEnabled)
             {
-                throw new ArgumentException("Quick connect is not active on this server");
+                throw new AuthenticationException("Quick connect is not active on this server");
             }
         }
 
-        /// <inheritdoc/>
-        public void Activate()
-        {
-            DateActivated = DateTime.UtcNow;
-            SetState(QuickConnectState.Active);
-        }
-
-        /// <inheritdoc/>
-        public void SetState(QuickConnectState newState)
-        {
-            _logger.LogDebug("Changed quick connect state from {State} to {newState}", State, newState);
-
-            ExpireRequests(true);
-
-            State = newState;
-            _config.Configuration.QuickConnectAvailable = newState == QuickConnectState.Available || newState == QuickConnectState.Active;
-            _config.SaveConfiguration();
-
-            _logger.LogDebug("Configuration saved");
-        }
-
         /// <inheritdoc/>
         public QuickConnectResult TryConnect()
         {
+            AssertActive();
             ExpireRequests();
 
-            if (State != QuickConnectState.Active)
-            {
-                _logger.LogDebug("Refusing quick connect initiation request, current state is {State}", State);
-                throw new AuthenticationException("Quick connect is not active on this server");
-            }
-
+            var secret = GenerateSecureRandom();
             var code = GenerateCode();
-            var result = new QuickConnectResult()
-            {
-                Secret = GenerateSecureRandom(),
-                DateAdded = DateTime.UtcNow,
-                Code = code
-            };
+            var result = new QuickConnectResult(secret, code, DateTime.UtcNow);
 
             _currentRequests[code] = result;
             return result;
@@ -116,12 +84,12 @@ namespace Emby.Server.Implementations.QuickConnect
         /// <inheritdoc/>
         public QuickConnectResult CheckRequestStatus(string secret)
         {
-            ExpireRequests();
             AssertActive();
+            ExpireRequests();
 
             string code = _currentRequests.Where(x => x.Value.Secret == secret).Select(x => x.Value.Code).DefaultIfEmpty(string.Empty).First();
 
-            if (!_currentRequests.TryGetValue(code, out QuickConnectResult result))
+            if (!_currentRequests.TryGetValue(code, out QuickConnectResult? result))
             {
                 throw new ResourceNotFoundException("Unable to find request with provided secret");
             }
@@ -129,22 +97,11 @@ namespace Emby.Server.Implementations.QuickConnect
             return result;
         }
 
-        /// <inheritdoc/>
-        public void AuthenticateRequest(AuthenticationRequest request, string token)
-        {
-            if (!_quickConnectTokens.TryGetValue(token, out var entry))
-            {
-                throw new SecurityException("Unknown quick connect token");
-            }
-
-            request.UserId = entry.UserId;
-            _quickConnectTokens.Remove(token, out _);
-
-            _sessionManager.AuthenticateQuickConnect(request, token);
-        }
-
-        /// <inheritdoc/>
-        public string GenerateCode()
+        /// <summary>
+        /// Generates a short code to display to the user to uniquely identify this request.
+        /// </summary>
+        /// <returns>A short, unique alphanumeric string.</returns>
+        private string GenerateCode()
         {
             Span<byte> raw = stackalloc byte[4];
 
@@ -163,12 +120,12 @@ namespace Emby.Server.Implementations.QuickConnect
         }
 
         /// <inheritdoc/>
-        public bool AuthorizeRequest(Guid userId, string code)
+        public async Task<bool> AuthorizeRequest(Guid userId, string code)
         {
-            ExpireRequests();
             AssertActive();
+            ExpireRequests();
 
-            if (!_currentRequests.TryGetValue(code, out QuickConnectResult result))
+            if (!_currentRequests.TryGetValue(code, out QuickConnectResult? result))
             {
                 throw new ResourceNotFoundException("Unable to find request");
             }
@@ -178,37 +135,19 @@ namespace Emby.Server.Implementations.QuickConnect
                 throw new InvalidOperationException("Request is already authorized");
             }
 
-            result.Authentication = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+            var token = Guid.NewGuid();
+            result.Authentication = token;
 
             // Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated.
-            var added = result.DateAdded ?? DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(Timeout));
-            result.DateAdded = added.Subtract(TimeSpan.FromMinutes(Timeout - 1));
+            result.DateAdded = DateTime.Now.Add(TimeSpan.FromMinutes(1));
 
-            _quickConnectTokens[result.Authentication] = (TokenName, userId);
+            await _sessionManager.AuthenticateQuickConnect(userId).ConfigureAwait(false);
 
             _logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId);
 
             return true;
         }
 
-        /// <inheritdoc/>
-        public int DeleteAllDevices(Guid user)
-        {
-            var tokens = _quickConnectTokens
-                .Where(entry => entry.Value.Token.StartsWith(TokenName, StringComparison.Ordinal) && entry.Value.UserId == user)
-                .ToList();
-
-            var removed = 0;
-            foreach (var token in tokens)
-            {
-                _quickConnectTokens.Remove(token.Key, out _);
-                _logger.LogDebug("Deleted token {AccessToken}", token.Key);
-                removed++;
-            }
-
-            return removed;
-        }
-
         /// <summary>
         /// Dispose.
         /// </summary>
@@ -226,7 +165,7 @@ namespace Emby.Server.Implementations.QuickConnect
         {
             if (disposing)
             {
-                _rng?.Dispose();
+                _rng.Dispose();
             }
         }
 
@@ -238,22 +177,19 @@ namespace Emby.Server.Implementations.QuickConnect
             return Convert.ToHexString(bytes);
         }
 
-        /// <inheritdoc/>
-        public void ExpireRequests(bool expireAll = false)
+        /// <summary>
+        /// Expire quick connect requests that are over the time limit. If <paramref name="expireAll"/> is true, all requests are unconditionally expired.
+        /// </summary>
+        /// <param name="expireAll">If true, all requests will be expired.</param>
+        private void ExpireRequests(bool expireAll = false)
         {
-            // Check if quick connect should be deactivated
-            if (State == QuickConnectState.Active && DateTime.UtcNow > DateActivated.AddMinutes(Timeout) && !expireAll)
-            {
-                _logger.LogDebug("Quick connect time expired, deactivating");
-                SetState(QuickConnectState.Available);
-                expireAll = true;
-            }
+            // All requests before this timestamp have expired
+            var minTime = DateTime.UtcNow.AddMinutes(-Timeout);
 
             // Expire stale connection requests
             foreach (var (_, currentRequest) in _currentRequests)
             {
-                var added = currentRequest.DateAdded ?? DateTime.UnixEpoch;
-                if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout))
+                if (expireAll || currentRequest.DateAdded > minTime)
                 {
                     var code = currentRequest.Code;
                     _logger.LogDebug("Removing expired request {Code}", code);
@@ -265,10 +201,5 @@ namespace Emby.Server.Implementations.QuickConnect
                 }
             }
         }
-
-        private void ReloadConfiguration()
-        {
-            State = _config.Configuration.QuickConnectAvailable ? QuickConnectState.Available : QuickConnectState.Unavailable;
-        }
     }
 }

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

@@ -12,7 +12,7 @@ using System.Threading.Tasks;
 using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;

+ 3 - 2
Emby.Server.Implementations/Session/SessionManager.cs

@@ -14,6 +14,7 @@ using Jellyfin.Data.Entities.Security;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Events;
 using Jellyfin.Data.Queries;
+using Jellyfin.Extensions;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller;
@@ -1438,9 +1439,9 @@ namespace Emby.Server.Implementations.Session
             return AuthenticateNewSessionInternal(request, true);
         }
 
-        public Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token)
+        public Task<AuthenticationResult> AuthenticateQuickConnect(Guid userId)
         {
-            return AuthenticateNewSessionInternal(request, false);
+            return AuthenticateNewSessionInternal(new AuthenticationRequest { UserId = userId }, false);
         }
 
         private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)

+ 2 - 1
Emby.Server.Implementations/Sorting/StudioComparer.cs

@@ -4,6 +4,7 @@
 
 using System;
 using System.Linq;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Querying;
@@ -30,7 +31,7 @@ namespace Emby.Server.Implementations.Sorting
                 throw new ArgumentNullException(nameof(y));
             }
 
-            return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty);
+            return AlphanumericComparator.CompareValues(x.Studios.FirstOrDefault(), y.Studios.FirstOrDefault());
         }
 
         /// <summary>

+ 1 - 1
Emby.Server.Implementations/Updates/InstallationManager.cs

@@ -11,7 +11,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Data.Events;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Updates;

+ 1 - 1
Jellyfin.Api/BaseJellyfinApiController.cs

@@ -1,5 +1,5 @@
 using System.Net.Mime;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using Microsoft.AspNetCore.Mvc;
 
 namespace Jellyfin.Api

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

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Models.ConfigurationDtos;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Configuration;

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

@@ -8,8 +8,8 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Models.PluginDtos;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Json;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Model.Net;

+ 23 - 59
Jellyfin.Api/Controllers/QuickConnectController.cs

@@ -1,7 +1,9 @@
 using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Helpers;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.QuickConnect;
 using MediaBrowser.Model.QuickConnect;
 using Microsoft.AspNetCore.Authorization;
@@ -30,13 +32,12 @@ namespace Jellyfin.Api.Controllers
         /// Gets the current quick connect state.
         /// </summary>
         /// <response code="200">Quick connect state returned.</response>
-        /// <returns>The current <see cref="QuickConnectState"/>.</returns>
-        [HttpGet("Status")]
+        /// <returns>Whether Quick Connect is enabled on the server or not.</returns>
+        [HttpGet("Enabled")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QuickConnectState> GetStatus()
+        public ActionResult<bool> GetEnabled()
         {
-            _quickConnect.ExpireRequests();
-            return _quickConnect.State;
+            return _quickConnect.IsEnabled;
         }
 
         /// <summary>
@@ -49,7 +50,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QuickConnectResult> Initiate()
         {
-            return _quickConnect.TryConnect();
+            try
+            {
+                return _quickConnect.TryConnect();
+            }
+            catch (AuthenticationException)
+            {
+                return Unauthorized("Quick connect is disabled");
+            }
         }
 
         /// <summary>
@@ -72,42 +80,10 @@ namespace Jellyfin.Api.Controllers
             {
                 return NotFound("Unknown secret");
             }
-        }
-
-        /// <summary>
-        /// Temporarily activates quick connect for five minutes.
-        /// </summary>
-        /// <response code="204">Quick connect has been temporarily activated.</response>
-        /// <response code="403">Quick connect is unavailable on this server.</response>
-        /// <returns>An <see cref="NoContentResult"/> on success.</returns>
-        [HttpPost("Activate")]
-        [Authorize(Policy = Policies.DefaultAuthorization)]
-        [ProducesResponseType(StatusCodes.Status204NoContent)]
-        [ProducesResponseType(StatusCodes.Status403Forbidden)]
-        public ActionResult Activate()
-        {
-            if (_quickConnect.State == QuickConnectState.Unavailable)
+            catch (AuthenticationException)
             {
-                return StatusCode(StatusCodes.Status403Forbidden, "Quick connect is unavailable");
+                return Unauthorized("Quick connect is disabled");
             }
-
-            _quickConnect.Activate();
-            return NoContent();
-        }
-
-        /// <summary>
-        /// Enables or disables quick connect.
-        /// </summary>
-        /// <param name="status">New <see cref="QuickConnectState"/>.</param>
-        /// <response code="204">Quick connect state set successfully.</response>
-        /// <returns>An <see cref="NoContentResult"/> on success.</returns>
-        [HttpPost("Available")]
-        [Authorize(Policy = Policies.RequiresElevation)]
-        [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult Available([FromQuery] QuickConnectState status = QuickConnectState.Available)
-        {
-            _quickConnect.SetState(status);
-            return NoContent();
         }
 
         /// <summary>
@@ -121,7 +97,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
-        public ActionResult<bool> Authorize([FromQuery, Required] string code)
+        public async Task<ActionResult<bool>> Authorize([FromQuery, Required] string code)
         {
             var userId = ClaimHelpers.GetUserId(Request.HttpContext.User);
             if (!userId.HasValue)
@@ -129,26 +105,14 @@ namespace Jellyfin.Api.Controllers
                 return StatusCode(StatusCodes.Status403Forbidden, "Unknown user id");
             }
 
-            return _quickConnect.AuthorizeRequest(userId.Value, code);
-        }
-
-        /// <summary>
-        /// Deauthorize all quick connect devices for the current user.
-        /// </summary>
-        /// <response code="200">All quick connect devices were deleted.</response>
-        /// <returns>The number of devices that were deleted.</returns>
-        [HttpPost("Deauthorize")]
-        [Authorize(Policy = Policies.DefaultAuthorization)]
-        [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<int> Deauthorize()
-        {
-            var userId = ClaimHelpers.GetUserId(Request.HttpContext.User);
-            if (!userId.HasValue)
+            try
             {
-                return 0;
+                return await _quickConnect.AuthorizeRequest(userId.Value, code).ConfigureAwait(false);
+            }
+            catch (AuthenticationException)
+            {
+                return Unauthorized("Quick connect is disabled");
             }
-
-            return _quickConnect.DeleteAllDevices(userId.Value);
         }
     }
 }

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

@@ -6,7 +6,7 @@ using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;

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

@@ -9,7 +9,7 @@ using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;

+ 1 - 1
Jellyfin.Api/Extensions/DtoExtensions.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 using Jellyfin.Api.Helpers;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;

+ 1 - 1
Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs

@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using System.Text.Json.Serialization;
 using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 

+ 1 - 1
Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Text.Json.Serialization;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
 
 namespace Jellyfin.Api.Models.PlaylistDtos
 {

+ 2 - 2
Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Text.Json.Serialization;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Session;
 
@@ -85,4 +85,4 @@ namespace Jellyfin.Api.Models.SessionDtos
             };
         }
     }
-}
+}

+ 2 - 5
Jellyfin.Drawing.Skia/SkiaEncoder.cs

@@ -3,10 +3,10 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using BlurHashSharp.SkiaSharp;
+using Diacritics.Extensions;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Model.Drawing;
 using Microsoft.Extensions.Logging;
 using SkiaSharp;
@@ -142,9 +142,6 @@ namespace Jellyfin.Drawing.Skia
             return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
         }
 
-        private static bool HasDiacritics(string text)
-            => !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
-
         private bool RequiresSpecialCharacterHack(string path)
         {
             for (int i = 0; i < path.Length; i++)
@@ -155,7 +152,7 @@ namespace Jellyfin.Drawing.Skia
                 }
             }
 
-            return HasDiacritics(path);
+            return path.HasDiacritics();
         }
 
         private string NormalizePath(string path)

+ 1 - 1
Jellyfin.Server.Implementations/Security/AuthorizationContext.cs

@@ -5,7 +5,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using Microsoft.AspNetCore.Http;

+ 1 - 1
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -21,11 +21,11 @@ using Jellyfin.Api.Constants;
 using Jellyfin.Api.Controllers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Enums;
+using Jellyfin.Extensions.Json;
 using Jellyfin.Networking.Configuration;
 using Jellyfin.Server.Configuration;
 using Jellyfin.Server.Filters;
 using Jellyfin.Server.Formatters;
-using MediaBrowser.Common.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Entities;
 using Microsoft.AspNetCore.Authentication;

+ 1 - 1
Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs

@@ -1,4 +1,4 @@
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.Net.Http.Headers;
 

+ 1 - 1
Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs

@@ -1,5 +1,5 @@
 using System.Net.Mime;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.Net.Http.Headers;
 

+ 1 - 1
Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs

@@ -2,7 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.Extensions.Primitives;

+ 1 - 1
Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs

@@ -4,9 +4,9 @@ using Emby.Server.Implementations.Data;
 using Emby.Server.Implementations.Serialization;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
+using Jellyfin.Extensions.Json;
 using Jellyfin.Server.Implementations;
 using Jellyfin.Server.Implementations.Users;
-using MediaBrowser.Common.Json;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Configuration;

+ 16 - 0
Jellyfin.sln

@@ -83,6 +83,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions", "src\Jellyfin.Extensions\Jellyfin.Extensions.csproj", "{750B8757-BE3D-4F8C-941A-FBAD94904ADA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -229,6 +235,14 @@ Global
 		{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
+		{750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -247,6 +261,8 @@ Global
 		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {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}
+		{750B8757-BE3D-4F8C-941A-FBAD94904ADA} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
+		{332A5C7A-F907-47CA-910E-BE6F7371B9E0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}

+ 1 - 1
MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs

@@ -3,7 +3,7 @@
 using System;
 using System.Linq;
 using System.Threading;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;

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

@@ -8,9 +8,9 @@ using System.Linq;
 using System.Text.Json.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using Diacritics.Extensions;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
-using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using Microsoft.Extensions.Logging;

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

@@ -5,7 +5,7 @@
 using System;
 using System.Collections.Generic;
 using System.Text.Json.Serialization;
-using MediaBrowser.Controller.Extensions;
+using Diacritics.Extensions;
 using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Controller.Entities.Audio

+ 2 - 1
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -11,13 +11,14 @@ using System.Text;
 using System.Text.Json.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using Diacritics.Extensions;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;

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

@@ -10,7 +10,7 @@ using System.Text.Json;
 using System.Text.Json.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;

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

@@ -2,7 +2,7 @@
 
 using System;
 using System.Linq;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Entities

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

@@ -5,8 +5,8 @@
 using System;
 using System.Collections.Generic;
 using System.Text.Json.Serialization;
+using Diacritics.Extensions;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Extensions;
 using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Controller.Entities

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

@@ -5,7 +5,7 @@
 using System;
 using System.Collections.Generic;
 using System.Text.Json.Serialization;
-using MediaBrowser.Controller.Extensions;
+using Diacritics.Extensions;
 using MediaBrowser.Controller.Providers;
 using Microsoft.Extensions.Logging;
 

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

@@ -5,7 +5,7 @@
 using System;
 using System.Collections.Generic;
 using System.Text.Json.Serialization;
-using MediaBrowser.Controller.Extensions;
+using Diacritics.Extensions;
 using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Controller.Entities

+ 0 - 73
MediaBrowser.Controller/Extensions/StringExtensions.cs

@@ -1,73 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-
-namespace MediaBrowser.Controller.Extensions
-{
-    /// <summary>
-    /// Class BaseExtensions.
-    /// </summary>
-    public static class StringExtensions
-    {
-        public static string RemoveDiacritics(this string text)
-        {
-            var chars = Normalize(text, NormalizationForm.FormD)
-                .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark);
-
-            return Normalize(string.Concat(chars), NormalizationForm.FormC);
-        }
-
-        /// <summary>
-        /// Counts the number of occurrences of [needle] in the string.
-        /// </summary>
-        /// <param name="value">The haystack to search in.</param>
-        /// <param name="needle">The character to search for.</param>
-        /// <returns>The number of occurrences of the [needle] character.</returns>
-        public static int Count(this ReadOnlySpan<char> value, char needle)
-        {
-            var count = 0;
-            var length = value.Length;
-            for (var i = 0; i < length; i++)
-            {
-                if (value[i] == needle)
-                {
-                    count++;
-                }
-            }
-
-            return count;
-        }
-
-        private static string Normalize(string text, NormalizationForm form, bool stripStringOnFailure = true)
-        {
-            if (stripStringOnFailure)
-            {
-                try
-                {
-                    return text.Normalize(form);
-                }
-                catch (ArgumentException)
-                {
-                    // will throw if input contains invalid unicode chars
-                    // 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])", string.Empty);
-                    return Normalize(text, form, false);
-                }
-            }
-
-            try
-            {
-                return text.Normalize(form);
-            }
-            catch (ArgumentException)
-            {
-                // if it still fails, return the original text
-                return text;
-            }
-        }
-    }
-}

+ 1 - 0
MediaBrowser.Controller/Library/NameExtensions.cs

@@ -3,6 +3,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Diacritics.Extensions;
 using MediaBrowser.Controller.Extensions;
 
 namespace MediaBrowser.Controller.Library

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -14,6 +14,7 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <PackageReference Include="Diacritics" Version="2.1.20036.1" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />

+ 4 - 62
MediaBrowser.Controller/QuickConnect/IQuickConnect.cs

@@ -1,6 +1,5 @@
-#nullable disable
-
 using System;
+using System.Threading.Tasks;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.QuickConnect;
 
@@ -12,40 +11,9 @@ namespace MediaBrowser.Controller.QuickConnect
     public interface IQuickConnect
     {
         /// <summary>
-        /// Gets or sets the length of user facing codes.
-        /// </summary>
-        int CodeLength { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name of internal access tokens.
-        /// </summary>
-        string TokenName { get; set; }
-
-        /// <summary>
-        /// Gets the current state of quick connect.
-        /// </summary>
-        QuickConnectState State { get; }
-
-        /// <summary>
-        /// Gets or sets the time (in minutes) before quick connect will automatically deactivate.
-        /// </summary>
-        int Timeout { get; set; }
-
-        /// <summary>
-        /// Assert that quick connect is currently active and throws an exception if it is not.
-        /// </summary>
-        void AssertActive();
-
-        /// <summary>
-        /// Temporarily activates quick connect for a short amount of time.
+        /// Gets a value indicating whether quick connect is enabled or not.
         /// </summary>
-        void Activate();
-
-        /// <summary>
-        /// Changes the state of quick connect.
-        /// </summary>
-        /// <param name="newState">New state to change to.</param>
-        void SetState(QuickConnectState newState);
+        bool IsEnabled { get; }
 
         /// <summary>
         /// Initiates a new quick connect request.
@@ -60,38 +28,12 @@ namespace MediaBrowser.Controller.QuickConnect
         /// <returns>Quick connect result.</returns>
         QuickConnectResult CheckRequestStatus(string secret);
 
-        /// <summary>
-        /// Authenticates a QuickConnect request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <param name="token">The token.</param>
-        void AuthenticateRequest(AuthenticationRequest request, string token);
-
         /// <summary>
         /// Authorizes a quick connect request to connect as the calling user.
         /// </summary>
         /// <param name="userId">User id.</param>
         /// <param name="code">Identifying code for the request.</param>
         /// <returns>A boolean indicating if the authorization completed successfully.</returns>
-        bool AuthorizeRequest(Guid userId, string code);
-
-        /// <summary>
-        /// Expire quick connect requests that are over the time limit. If <paramref name="expireAll"/> is true, all requests are unconditionally expired.
-        /// </summary>
-        /// <param name="expireAll">If true, all requests will be expired.</param>
-        void ExpireRequests(bool expireAll = false);
-
-        /// <summary>
-        /// Deletes all quick connect access tokens for the provided user.
-        /// </summary>
-        /// <param name="user">Guid of the user to delete tokens for.</param>
-        /// <returns>A count of the deleted tokens.</returns>
-        int DeleteAllDevices(Guid user);
-
-        /// <summary>
-        /// Generates a short code to display to the user to uniquely identify this request.
-        /// </summary>
-        /// <returns>A short, unique alphanumeric string.</returns>
-        string GenerateCode();
+        Task<bool> AuthorizeRequest(Guid userId, string code);
     }
 }

+ 2 - 3
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -276,10 +276,9 @@ namespace MediaBrowser.Controller.Session
         /// <summary>
         /// Authenticates a new session with quick connect.
         /// </summary>
-        /// <param name="request">The request.</param>
-        /// <param name="token">Quick connect access token.</param>
+        /// <param name="userId">The user id.</param>
         /// <returns>Task{SessionInfo}.</returns>
-        Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token);
+        Task<AuthenticationResult> AuthenticateQuickConnect(Guid userId);
 
         /// <summary>
         /// Reports the capabilities.

+ 2 - 3
MediaBrowser.Controller/Sorting/SortExtensions.cs

@@ -1,16 +1,15 @@
-#nullable disable
-
 #pragma warning disable CS1591
 
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Jellyfin.Extensions;
 
 namespace MediaBrowser.Controller.Sorting
 {
     public static class SortExtensions
     {
-        private static readonly AlphanumComparator _comparer = new AlphanumComparator();
+        private static readonly AlphanumericComparator _comparer = new AlphanumericComparator();
 
         public static IEnumerable<T> OrderByString<T>(this IEnumerable<T> list, Func<T, string> getName)
         {

+ 1 - 1
MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs

@@ -2,7 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;

+ 1 - 1
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -11,9 +11,9 @@ using System.Text.Json;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.MediaEncoding.Probing;

+ 1 - 1
MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs

@@ -2,7 +2,7 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Threading;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Model.MediaInfo;
 using Microsoft.Extensions.Logging;
 using Nikse.SubtitleEdit.Core;

+ 7 - 4
MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs

@@ -2,25 +2,28 @@
 
 namespace MediaBrowser.Model.Dlna
 {
+    /// <summary>
+    /// Delivery method to use during playback of a specific subtitle format.
+    /// </summary>
     public enum SubtitleDeliveryMethod
     {
         /// <summary>
-        /// The encode.
+        /// Burn the subtitles in the video track.
         /// </summary>
         Encode = 0,
 
         /// <summary>
-        /// The embed.
+        /// Embed the subtitles in the file or stream.
         /// </summary>
         Embed = 1,
 
         /// <summary>
-        /// The external.
+        /// Serve the subtitles as an external file.
         /// </summary>
         External = 2,
 
         /// <summary>
-        /// The HLS.
+        /// Serve the subtitles as a separate HLS stream.
         /// </summary>
         Hls = 3
     }

+ 1 - 0
MediaBrowser.Model/Entities/VirtualFolderInfo.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Text.Json.Serialization;
+using Jellyfin.Extensions.Json.Converters;
 using MediaBrowser.Model.Configuration;
 
 namespace MediaBrowser.Model.Entities

+ 2 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -50,7 +50,8 @@
     <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" />
+    <ProjectReference Include="../Jellyfin.Data/Jellyfin.Data.csproj" />
+    <ProjectReference Include="../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" />
   </ItemGroup>
 
 </Project>

+ 20 - 12
MediaBrowser.Model/QuickConnect/QuickConnectResult.cs

@@ -3,38 +3,46 @@ using System;
 namespace MediaBrowser.Model.QuickConnect
 {
     /// <summary>
-    /// Stores the result of an incoming quick connect request.
+    /// Stores the state of an quick connect request.
     /// </summary>
     public class QuickConnectResult
     {
         /// <summary>
-        /// Gets a value indicating whether this request is authorized.
+        /// Initializes a new instance of the <see cref="QuickConnectResult"/> class.
         /// </summary>
-        public bool Authenticated => !string.IsNullOrEmpty(Authentication);
+        /// <param name="secret">The secret used to query the request state.</param>
+        /// <param name="code">The code used to allow the request.</param>
+        /// <param name="dateAdded">The time when the request was created.</param>
+        public QuickConnectResult(string secret, string code, DateTime dateAdded)
+        {
+            Secret = secret;
+            Code = code;
+            DateAdded = dateAdded;
+        }
 
         /// <summary>
-        /// Gets or sets the secret value used to uniquely identify this request. Can be used to retrieve authentication information.
+        /// Gets a value indicating whether this request is authorized.
         /// </summary>
-        public string? Secret { get; set; }
+        public bool Authenticated => Authentication != null;
 
         /// <summary>
-        /// Gets or sets the user facing code used so the user can quickly differentiate this request from others.
+        /// Gets the secret value used to uniquely identify this request. Can be used to retrieve authentication information.
         /// </summary>
-        public string? Code { get; set; }
+        public string Secret { get; }
 
         /// <summary>
-        /// Gets or sets the private access token.
+        /// Gets the user facing code used so the user can quickly differentiate this request from others.
         /// </summary>
-        public string? Authentication { get; set; }
+        public string Code { get; }
 
         /// <summary>
-        /// Gets or sets an error message.
+        /// Gets or sets the private access token.
         /// </summary>
-        public string? Error { get; set; }
+        public Guid? Authentication { get; set; }
 
         /// <summary>
         /// Gets or sets the DateTime that this request was created.
         /// </summary>
-        public DateTime? DateAdded { get; set; }
+        public DateTime DateAdded { get; set; }
     }
 }

+ 0 - 23
MediaBrowser.Model/QuickConnect/QuickConnectState.cs

@@ -1,23 +0,0 @@
-namespace MediaBrowser.Model.QuickConnect
-{
-    /// <summary>
-    /// Quick connect state.
-    /// </summary>
-    public enum QuickConnectState
-    {
-        /// <summary>
-        /// This feature has not been opted into and is unavailable until the server administrator chooses to opt-in.
-        /// </summary>
-        Unavailable = 0,
-
-        /// <summary>
-        /// The feature is enabled for use on the server but is not currently accepting connection requests.
-        /// </summary>
-        Available = 1,
-
-        /// <summary>
-        /// The feature is actively accepting connection requests.
-        /// </summary>
-        Active = 2
-    }
-}

+ 1 - 1
MediaBrowser.Providers/Manager/ProviderUtils.cs

@@ -3,9 +3,9 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Diacritics.Extensions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 

+ 1 - 1
MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs

@@ -6,7 +6,7 @@ using System.Net.Http;
 using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;

+ 1 - 1
MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs

@@ -11,7 +11,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Audio;

+ 1 - 1
MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs

@@ -6,7 +6,7 @@ using System.Net.Http;
 using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;

+ 1 - 1
MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs

@@ -10,7 +10,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Audio;

+ 2 - 12
MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs

@@ -11,8 +11,8 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
+using Diacritics.Extensions;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
@@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.Music
                     }
                 }
 
-                if (HasDiacritics(searchInfo.Name))
+                if (searchInfo.Name.HasDiacritics())
                 {
                     // Try again using the search with accent characters url
                     url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
@@ -251,16 +251,6 @@ namespace MediaBrowser.Providers.Music
             return result;
         }
 
-        /// <summary>
-        /// Determines whether the specified text has diacritics.
-        /// </summary>
-        /// <param name="text">The text.</param>
-        /// <returns><c>true</c> if the specified text has diacritics; otherwise, <c>false</c>.</returns>
-        private bool HasDiacritics(string text)
-        {
-            return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
-        }
-
         /// <summary>
         /// Encodes an URL.
         /// </summary>

+ 4 - 2
MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs → MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs

@@ -1,9 +1,11 @@
-using System;
+#nullable enable
+
+using System;
 using System.ComponentModel;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace MediaBrowser.Providers.Plugins.Omdb
 {
     /// <summary>
     /// Converts a string <c>N/A</c> to <c>string.Empty</c>.

+ 4 - 2
MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs → MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs

@@ -1,8 +1,10 @@
-using System;
+#nullable enable
+
+using System;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace MediaBrowser.Providers.Plugins.Omdb
 {
     /// <summary>
     /// Converts a string <c>N/A</c> to <c>string.Empty</c>.

+ 2 - 2
MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs

@@ -10,8 +10,8 @@ using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common;
-using MediaBrowser.Common.Json;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json;
+using Jellyfin.Extensions.Json.Converters;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;

+ 2 - 2
MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs

@@ -10,8 +10,8 @@ using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common;
-using MediaBrowser.Common.Json;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json;
+using Jellyfin.Extensions.Json.Converters;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;

+ 1 - 1
MediaBrowser.Providers/Properties/AssemblyInfo.cs

@@ -15,7 +15,7 @@ using System.Runtime.InteropServices;
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
 [assembly: NeutralResourcesLanguage("en")]
-[assembly: InternalsVisibleTo("Jellyfin.Common.Tests")]
+[assembly: InternalsVisibleTo("Jellyfin.Providers.Tests")]
 
 // Setting ComVisible to false makes the types in this assembly not visible
 // to COM components.  If you need to access a type in this assembly from

+ 1 - 1
MediaBrowser.Providers/Studios/StudiosImageProvider.cs

@@ -8,7 +8,7 @@ using System.Linq;
 using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;

+ 1 - 1
debian/rules

@@ -39,7 +39,7 @@ override_dh_auto_test:
 override_dh_clistrip:
 
 override_dh_auto_build:
-	dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
+	dotnet publish -maxcpucount:1 --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
 		"-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
 
 override_dh_auto_clean:

+ 11 - 4
MediaBrowser.Controller/Sorting/AlphanumComparator.cs → src/Jellyfin.Extensions/AlphanumericComparator.cs

@@ -1,12 +1,19 @@
-#pragma warning disable CS1591
-
 using System;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Controller.Sorting
+namespace Jellyfin.Extensions
 {
-    public class AlphanumComparator : IComparer<string?>
+    /// <summary>
+    /// Alphanumeric <see cref="IComparer{T}" />.
+    /// </summary>
+    public class AlphanumericComparator : IComparer<string?>
     {
+        /// <summary>
+        /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
+        /// </summary>
+        /// <param name="s1">The first object to compare.</param>
+        /// <param name="s2">The second object to compare.</param>
+        /// <returns>A signed integer that indicates the relative values of <c>x</c> and <c>y</c>.</returns>
         public static int CompareValues(string? s1, string? s2)
         {
             if (s1 == null && s2 == null)

+ 1 - 1
MediaBrowser.Common/Extensions/CopyToExtensions.cs → src/Jellyfin.Extensions/CopyToExtensions.cs

@@ -1,6 +1,6 @@
 using System.Collections.Generic;
 
-namespace MediaBrowser.Common.Extensions
+namespace Jellyfin.Extensions
 {
     /// <summary>
     /// Provides <c>CopyTo</c> extensions methods for <see cref="IReadOnlyList{T}" />.

+ 2 - 2
MediaBrowser.Common/Extensions/EnumerableExtensions.cs → src/Jellyfin.Extensions/EnumerableExtensions.cs

@@ -1,7 +1,7 @@
-using System;
+using System;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Common.Extensions
+namespace Jellyfin.Extensions
 {
     /// <summary>
     /// Static extensions for the <see cref="IEnumerable{T}"/> interface.

+ 30 - 0
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj

@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <Nullable>enable</Nullable>
+    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+    <CodeAnalysisRuleSet>../../jellyfin.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <Authors>Jellyfin Contributors</Authors>
+    <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
+    <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="../../SharedVersion.cs" />
+  </ItemGroup>
+
+  <!-- Code Analyzers-->
+  <ItemGroup>
+    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+  </ItemGroup>
+
+</Project>

+ 2 - 2
MediaBrowser.Common/Json/Converters/JsonBoolNumberConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonBoolNumberConverter.cs

@@ -2,7 +2,7 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Converts a number to a boolean.
@@ -27,4 +27,4 @@ namespace MediaBrowser.Common.Json.Converters
             writer.WriteBooleanValue(value);
         }
     }
-}
+}

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs

@@ -3,7 +3,7 @@ using System.ComponentModel;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Convert comma delimited string to array of type.

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs → src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs

@@ -2,7 +2,7 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Json comma delimited array converter factory.

+ 2 - 2
MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonDateTimeConverter.cs

@@ -3,7 +3,7 @@ using System.Globalization;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Legacy DateTime converter.
@@ -31,4 +31,4 @@ namespace MediaBrowser.Common.Json.Converters
             }
         }
     }
-}
+}

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs

@@ -3,7 +3,7 @@ using System.ComponentModel;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Convert delimited string to array of type.

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs

@@ -3,7 +3,7 @@ using System.Globalization;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Converts a GUID object or value to/from JSON.

+ 3 - 7
MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs

@@ -1,12 +1,8 @@
-#nullable disable
-// THIS IS A HACK
-// TODO: @bond Move to separate project
-
 using System;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Model.Entities
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Converts an object to a lowercase string.
@@ -15,7 +11,7 @@ namespace MediaBrowser.Model.Entities
     public class JsonLowerCaseConverter<T> : JsonConverter<T>
     {
         /// <inheritdoc />
-        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+        public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
         {
             return JsonSerializer.Deserialize<T>(ref reader, options);
         }
@@ -23,7 +19,7 @@ namespace MediaBrowser.Model.Entities
         /// <inheritdoc />
         public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
         {
-            writer.WriteStringValue(value?.ToString().ToLowerInvariant());
+            writer.WriteStringValue(value?.ToString()?.ToLowerInvariant());
         }
     }
 }

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs

@@ -3,7 +3,7 @@ using System.Globalization;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Converts a GUID object or value to/from JSON.

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs

@@ -2,7 +2,7 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Converts a nullable struct or value to/from JSON.

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs → src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverterFactory.cs

@@ -2,7 +2,7 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Json nullable struct converter factory.

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs

@@ -3,7 +3,7 @@ using System.ComponentModel;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Convert Pipe delimited string to array of type.

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs → src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs

@@ -2,7 +2,7 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Json Pipe delimited array converter factory.

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonStringConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs

@@ -4,7 +4,7 @@ using System.Text;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Converter to allow the serializer to read strings.

+ 1 - 1
MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs → src/Jellyfin.Extensions/Json/Converters/JsonVersionConverter.cs

@@ -2,7 +2,7 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace MediaBrowser.Common.Json.Converters
+namespace Jellyfin.Extensions.Json.Converters
 {
     /// <summary>
     /// Converts a Version object or value to/from JSON.

+ 2 - 2
MediaBrowser.Common/Json/JsonDefaults.cs → src/Jellyfin.Extensions/Json/JsonDefaults.cs

@@ -1,8 +1,8 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
 
-namespace MediaBrowser.Common.Json
+namespace Jellyfin.Extensions.Json
 {
     /// <summary>
     /// Helper class for having compatible JSON throughout the codebase.

+ 1 - 1
MediaBrowser.Common/Extensions/ShuffleExtensions.cs → src/Jellyfin.Extensions/ShuffleExtensions.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Common.Extensions
+namespace Jellyfin.Extensions
 {
     /// <summary>
     /// Provides <c>Shuffle</c> extensions methods for <see cref="IList{T}" />.

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor