aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-package.yml1
-rw-r--r--Emby.Dlna/DlnaManager.cs10
-rw-r--r--Emby.Dlna/Emby.Dlna.csproj4
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs29
-rw-r--r--Emby.Dlna/Service/BaseControlHandler.cs2
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs14
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs35
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj9
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs14
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs19
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs20
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs16
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs15
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs38
-rw-r--r--Emby.Server.Implementations/Localization/Core/bn.json11
-rw-r--r--Emby.Server.Implementations/Localization/Core/hi.json29
-rw-r--r--Emby.Server.Implementations/Localization/Core/hr.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/kk.json12
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs17
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs42
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/TaskManager.cs8
-rw-r--r--Emby.Server.Implementations/Serialization/JsonSerializer.cs281
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs121
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs28
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs7
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj2
-rw-r--r--Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs55
-rw-r--r--Jellyfin.Drawing.Skia/StripCollageBuilder.cs13
-rw-r--r--Jellyfin.Networking/Manager/NetworkManager.cs11
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj1
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs15
-rw-r--r--Jellyfin.Server/Program.cs3
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs34
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs33
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs35
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStructConverter.cs35
-rw-r--r--MediaBrowser.Common/Json/JsonDefaults.cs78
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj2
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs7
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs30
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj2
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs17
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs6
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
-rw-r--r--MediaBrowser.Model/Serialization/IJsonSerializer.cs102
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs11
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs10
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs11
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs10
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs8
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs20
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs44
-rw-r--r--MediaBrowser.Providers/Properties/AssemblyInfo.cs2
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj1
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs69
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs68
62 files changed, 876 insertions, 681 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 606385116..0a63b329b 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -210,6 +210,7 @@ jobs:
- task: DotNetCoreCLI@2
displayName: 'Build Unstable Nuget packages'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
command: 'custom'
projects: |
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index fedd20b68..21ba1c755 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -7,12 +7,14 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
+using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Emby.Dlna.Profiles;
using Emby.Dlna.Server;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
@@ -32,9 +34,9 @@ namespace Emby.Dlna
private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILogger<DlnaManager> _logger;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
@@ -43,14 +45,12 @@ namespace Emby.Dlna
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILoggerFactory loggerFactory,
- IJsonSerializer jsonSerializer,
IServerApplicationHost appHost)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
_appPaths = appPaths;
_logger = loggerFactory.CreateLogger<DlnaManager>();
- _jsonSerializer = jsonSerializer;
_appHost = appHost;
}
@@ -495,9 +495,9 @@ namespace Emby.Dlna
return profile;
}
- var json = _jsonSerializer.SerializeToString(profile);
+ var json = JsonSerializer.Serialize(profile, _jsonOptions);
- return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
+ return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions);
}
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index bd30cc1e1..8b057a095 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -78,9 +78,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
- <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
</ItemGroup>
</Project>
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index fb4454a34..3f7b558f6 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.PlayTo;
using Emby.Dlna.Ssdp;
+using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
@@ -52,6 +53,8 @@ namespace Emby.Dlna.Main
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
private readonly object _syncLock = new object();
+ private readonly NetworkConfiguration _netConfig;
+ private readonly bool _disabled;
private PlayToManager _manager;
private SsdpDevicePublisher _publisher;
@@ -122,10 +125,22 @@ namespace Emby.Dlna.Main
httpClientFactory,
config);
Current = this;
+
+ _netConfig = config.GetConfiguration<NetworkConfiguration>("network");
+ _disabled = appHost.ListenWithHttps && _netConfig.RequireHttps;
+ if (_disabled)
+ {
+ _logger.LogError("The DLNA specification does not support HTTPS.");
+ }
}
public static DlnaEntryPoint Current { get; private set; }
+ /// <summary>
+ /// Gets a value indicating whether the dlna server is enabled.
+ /// </summary>
+ public static bool Enabled { get; private set; }
+
public IContentDirectory ContentDirectory { get; private set; }
public IConnectionManager ConnectionManager { get; private set; }
@@ -136,6 +151,12 @@ namespace Emby.Dlna.Main
{
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
+ if (_disabled)
+ {
+ // No use starting as dlna won't work, as we're running purely on HTTPS.
+ return;
+ }
+
ReloadComponents();
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
@@ -152,6 +173,7 @@ namespace Emby.Dlna.Main
private void ReloadComponents()
{
var options = _config.GetDlnaConfiguration();
+ Enabled = options.EnableServer;
StartSsdpHandler();
@@ -290,12 +312,15 @@ namespace Emby.Dlna.Main
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
- var uri = new Uri(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
+ var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
+ // DLNA will only work over http, so we must reset to http:// : {port}
+ uri.Scheme = "http://";
+ uri.Port = _netConfig.PublicPort;
var device = new SsdpRootDevice
{
CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
- Location = uri, // Must point to the URL that serves your devices UPnP description document.
+ Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document.
Address = address.Address,
PrefixLength = address.PrefixLength,
FriendlyName = "Jellyfin",
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
index 198852ec1..8d2486fee 100644
--- a/Emby.Dlna/Service/BaseControlHandler.cs
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -49,7 +49,7 @@ namespace Emby.Dlna.Service
{
ControlRequestInfo requestInfo = null;
- using (var streamReader = new StreamReader(request.InputXml))
+ using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
{
var readerSettings = new XmlReaderSettings()
{
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 50ef71a46..8fa712914 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -7,11 +7,11 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
-using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna;
@@ -50,6 +50,7 @@ using Jellyfin.Networking.Manager;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@@ -118,12 +119,12 @@ namespace Emby.Server.Implementations
private readonly IFileSystem _fileSystemManager;
private readonly IXmlSerializer _xmlSerializer;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IStartupOptions _startupOptions;
private IMediaEncoder _mediaEncoder;
private ISessionManager _sessionManager;
private string[] _urlPrefixes;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
/// <summary>
/// Gets a value indicating whether this instance can self restart.
@@ -257,7 +258,6 @@ namespace Emby.Server.Implementations
IServiceCollection serviceCollection)
{
_xmlSerializer = new MyXmlSerializer();
- _jsonSerializer = new JsonSerializer();
ServiceCollection = serviceCollection;
@@ -528,8 +528,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
- ServiceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
-
ServiceCollection.AddSingleton(_fileSystemManager);
ServiceCollection.AddSingleton<TmdbClientManager>();
@@ -754,7 +752,6 @@ namespace Emby.Server.Implementations
UserView.CollectionManager = Resolve<ICollectionManager>();
BaseItem.MediaSourceManager = Resolve<IMediaSourceManager>();
CollectionFolder.XmlSerializer = _xmlSerializer;
- CollectionFolder.JsonSerializer = Resolve<IJsonSerializer>();
CollectionFolder.ApplicationHost = this;
}
@@ -967,7 +964,7 @@ namespace Emby.Server.Implementations
{
return true;
}
-
+
throw new FileNotFoundException(
string.Format(
CultureInfo.InvariantCulture,
@@ -1051,7 +1048,8 @@ namespace Emby.Server.Implementations
var metafile = Path.Combine(dir, "meta.json");
if (File.Exists(metafile))
{
- var manifest = _jsonSerializer.DeserializeFromFile<PluginManifest>(metafile);
+ var jsonString = File.ReadAllText(metafile, Encoding.UTF8);
+ var manifest = JsonSerializer.Deserialize<PluginManifest>(jsonString, _jsonOptions);
if (!Version.TryParse(manifest.TargetAbi, out var targetAbi))
{
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 57684a429..2d5b19fa6 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -3,11 +3,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -21,7 +24,6 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
@@ -44,10 +46,10 @@ namespace Emby.Server.Implementations.Channels
private readonly ILogger<ChannelManager> _logger;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager;
private readonly IMemoryCache _memoryCache;
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
/// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
@@ -59,7 +61,6 @@ namespace Emby.Server.Implementations.Channels
/// <param name="config">The server configuration manager.</param>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="userDataManager">The user data manager.</param>
- /// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="providerManager">The provider manager.</param>
/// <param name="memoryCache">The memory cache.</param>
public ChannelManager(
@@ -70,7 +71,6 @@ namespace Emby.Server.Implementations.Channels
IServerConfigurationManager config,
IFileSystem fileSystem,
IUserDataManager userDataManager,
- IJsonSerializer jsonSerializer,
IProviderManager providerManager,
IMemoryCache memoryCache)
{
@@ -81,7 +81,6 @@ namespace Emby.Server.Implementations.Channels
_config = config;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
- _jsonSerializer = jsonSerializer;
_providerManager = providerManager;
_memoryCache = memoryCache;
}
@@ -343,7 +342,9 @@ namespace Emby.Server.Implementations.Channels
try
{
- return _jsonSerializer.DeserializeFromFile<List<MediaSourceInfo>>(path) ?? new List<MediaSourceInfo>();
+ var jsonString = File.ReadAllText(path, Encoding.UTF8);
+ return JsonSerializer.Deserialize<List<MediaSourceInfo>>(jsonString, _jsonOptions)
+ ?? new List<MediaSourceInfo>();
}
catch
{
@@ -351,7 +352,7 @@ namespace Emby.Server.Implementations.Channels
}
}
- private void SaveMediaSources(BaseItem item, List<MediaSourceInfo> mediaSources)
+ private async Task SaveMediaSources(BaseItem item, List<MediaSourceInfo> mediaSources)
{
var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json");
@@ -370,7 +371,8 @@ namespace Emby.Server.Implementations.Channels
Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(mediaSources, path);
+ await using FileStream createStream = File.Create(path);
+ await JsonSerializer.SerializeAsync(createStream, mediaSources, _jsonOptions).ConfigureAwait(false);
}
/// <inheritdoc />
@@ -812,7 +814,8 @@ namespace Emby.Server.Implementations.Channels
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
- var cachedResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
+ await using FileStream jsonStream = File.OpenRead(cachePath);
+ var cachedResult = await JsonSerializer.DeserializeAsync<ChannelItemResult>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (cachedResult != null)
{
return null;
@@ -834,7 +837,8 @@ namespace Emby.Server.Implementations.Channels
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
- var cachedResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
+ await using FileStream jsonStream = File.OpenRead(cachePath);
+ var cachedResult = await JsonSerializer.DeserializeAsync<ChannelItemResult>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (cachedResult != null)
{
return null;
@@ -865,7 +869,7 @@ namespace Emby.Server.Implementations.Channels
throw new InvalidOperationException("Channel returned a null result from GetChannelItems");
}
- CacheResponse(result, cachePath);
+ await CacheResponse(result, cachePath);
return result;
}
@@ -875,13 +879,14 @@ namespace Emby.Server.Implementations.Channels
}
}
- private void CacheResponse(object result, string path)
+ private async Task CacheResponse(object result, string path)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(result, path);
+ await using FileStream createStream = File.Create(path);
+ await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -1176,11 +1181,11 @@ namespace Emby.Server.Implementations.Channels
{
if (enableMediaProbe && !info.IsLiveStream && item.HasPathProtocol)
{
- SaveMediaSources(item, new List<MediaSourceInfo>());
+ await SaveMediaSources(item, new List<MediaSourceInfo>()).ConfigureAwait(false);
}
else
{
- SaveMediaSources(item, info.MediaSources);
+ await SaveMediaSources(item, info.MediaSources).ConfigureAwait(false);
}
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 9e9452f32..1e54c3b33 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -23,21 +23,12 @@
<ItemGroup>
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
- <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
- <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
- <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" 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="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
- <PackageReference Include="ServiceStack.Text.Core" Version="5.10.2" />
<PackageReference Include="sharpcompress" Version="0.26.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.0" />
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index 041619d1e..2070df31e 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -5,16 +5,17 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library
@@ -23,14 +24,13 @@ namespace Emby.Server.Implementations.Library
{
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger;
- private readonly IJsonSerializer _json;
private readonly IApplicationPaths _appPaths;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
- public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths)
+ public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths)
{
_mediaEncoder = mediaEncoder;
_logger = logger;
- _json = json;
_appPaths = appPaths;
}
@@ -47,7 +47,8 @@ namespace Emby.Server.Implementations.Library
{
try
{
- mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
+ await using FileStream jsonStream = File.OpenRead(cacheFilePath);
+ mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
// _logger.LogDebug("Found cached media info");
}
@@ -83,7 +84,8 @@ namespace Emby.Server.Implementations.Library
if (cacheFilePath != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
- _json.SerializeToFile(mediaInfo, cacheFilePath);
+ await using FileStream createStream = File.OpenWrite(cacheFilePath);
+ await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 928f5f88e..660ec106b 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -6,12 +6,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@@ -23,7 +25,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library
@@ -36,7 +37,6 @@ namespace Emby.Server.Implementations.Library
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILogger<MediaSourceManager> _logger;
private readonly IUserDataManager _userDataManager;
@@ -46,6 +46,7 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private IMediaSourceProvider[] _providers;
@@ -56,7 +57,6 @@ namespace Emby.Server.Implementations.Library
IUserManager userManager,
ILibraryManager libraryManager,
ILogger<MediaSourceManager> logger,
- IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IUserDataManager userDataManager,
IMediaEncoder mediaEncoder)
@@ -65,7 +65,6 @@ namespace Emby.Server.Implementations.Library
_userManager = userManager;
_libraryManager = libraryManager;
_logger = logger;
- _jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
_mediaEncoder = mediaEncoder;
@@ -504,7 +503,7 @@ namespace Emby.Server.Implementations.Library
// hack - these two values were taken from LiveTVMediaSourceProvider
string cacheKey = request.OpenToken;
- await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _appPaths)
+ await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
.ConfigureAwait(false);
}
@@ -516,9 +515,9 @@ namespace Emby.Server.Implementations.Library
}
// TODO: @bond Fix
- var json = _jsonSerializer.SerializeToString(mediaSource);
+ var json = JsonSerializer.Serialize(mediaSource, _jsonOptions);
_logger.LogInformation("Live stream opened: " + json);
- var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
+ var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
if (!request.UserId.Equals(Guid.Empty))
{
@@ -643,7 +642,8 @@ namespace Emby.Server.Implementations.Library
{
try
{
- mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
+ await using FileStream jsonStream = File.OpenRead(cacheFilePath);
+ mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
// _logger.LogDebug("Found cached media info");
}
@@ -679,7 +679,8 @@ namespace Emby.Server.Implementations.Library
if (cacheFilePath != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
- _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
+ await using FileStream createStream = File.Create(cacheFilePath);
+ await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 1d9529dff..94602582b 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library
if (query.Limit.HasValue)
{
- results = results.GetRange(0, query.Limit.Value);
+ results = results.GetRange(0, Math.Min(query.Limit.Value, results.Count));
}
return new QueryResult<SearchHintInfo>
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index f9e5e6bbc..d16275b19 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using Book = MediaBrowser.Controller.Entities.Book;
+using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
namespace Emby.Server.Implementations.Library
{
@@ -219,7 +220,7 @@ namespace Emby.Server.Implementations.Library
var hasRuntime = runtimeTicks > 0;
// If a position has been reported, and if we know the duration
- if (positionTicks > 0 && hasRuntime)
+ if (positionTicks > 0 && hasRuntime && !(item is AudioBook))
{
var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100;
@@ -245,6 +246,23 @@ namespace Emby.Server.Implementations.Library
}
}
}
+ else if (positionTicks > 0 && hasRuntime && item is AudioBook)
+ {
+ var minIn = TimeSpan.FromTicks(positionTicks).TotalMinutes;
+ var minOut = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes;
+
+ if (minIn > _config.Configuration.MinAudiobookResume)
+ {
+ // ignore progress during the beginning
+ positionTicks = 0;
+ }
+ else if (minOut < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks)
+ {
+ // mark as completed close to the end
+ positionTicks = 0;
+ data.Played = playedToCompletion = true;
+ }
+ }
else if (!hasRuntime)
{
// If we don't know the runtime we'll just have to assume it was fully played
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 0dc045ee6..2c0de661d 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -36,7 +36,6 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
@@ -51,7 +50,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly ILogger<EmbyTV> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config;
- private readonly IJsonSerializer _jsonSerializer;
private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider;
private readonly TimerManager _timerProvider;
@@ -81,7 +79,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IStreamHelper streamHelper,
IMediaSourceManager mediaSourceManager,
ILogger<EmbyTV> logger,
- IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
IServerConfigurationManager config,
ILiveTvManager liveTvManager,
@@ -103,12 +100,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_providerManager = providerManager;
_mediaEncoder = mediaEncoder;
_liveTvManager = (LiveTvManager)liveTvManager;
- _jsonSerializer = jsonSerializer;
_mediaSourceManager = mediaSourceManager;
_streamHelper = streamHelper;
- _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers.json"));
- _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers.json"));
+ _seriesTimerProvider = new SeriesTimerManager(_logger, Path.Combine(DataPath, "seriestimers.json"));
+ _timerProvider = new TimerManager(_logger, Path.Combine(DataPath, "timers.json"));
_timerProvider.TimerFired += OnTimerProviderTimerFired;
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
@@ -1052,7 +1048,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IgnoreIndex = true
};
- await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths)
+ await new LiveStreamHelper(_mediaEncoder, _logger, _config.CommonApplicationPaths)
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
return new List<MediaSourceInfo>
@@ -1635,7 +1631,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
{
- return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
+ return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _config);
}
return new DirectRecorder(_logger, _httpClientFactory, _streamHelper);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index e6ee9819e..78a82118e 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -6,16 +6,17 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
@@ -25,10 +26,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationPaths _appPaths;
- private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _serverConfigurationManager;
-
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private bool _hasExited;
private Stream _logFileStream;
private string _targetPath;
@@ -38,13 +38,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
ILogger logger,
IMediaEncoder mediaEncoder,
IServerApplicationPaths appPaths,
- IJsonSerializer json,
IServerConfigurationManager serverConfigurationManager)
{
_logger = logger;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
- _json = json;
_serverConfigurationManager = serverConfigurationManager;
}
@@ -66,7 +64,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Recording completed to file {0}", targetFile);
}
- private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
_targetPath = targetFile;
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
@@ -95,8 +93,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
_logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
- var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
- _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
+ await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false);
_process = new Process
{
@@ -115,8 +113,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
-
- return _taskCompletionSource.Task;
}
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile, TimeSpan duration)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index fc543dc55..c80ecd6b3 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -4,7 +4,10 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.Serialization;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
@@ -12,18 +15,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public class ItemDataProvider<T>
where T : class
{
- private readonly IJsonSerializer _jsonSerializer;
private readonly string _dataPath;
private readonly object _fileDataLock = new object();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private T[] _items;
public ItemDataProvider(
- IJsonSerializer jsonSerializer,
ILogger logger,
string dataPath,
Func<T, T, bool> equalityComparer)
{
- _jsonSerializer = jsonSerializer;
Logger = logger;
_dataPath = dataPath;
EqualityComparer = equalityComparer;
@@ -46,7 +47,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
- _items = _jsonSerializer.DeserializeFromFile<T[]>(_dataPath);
+ var jsonString = File.ReadAllText(_dataPath, Encoding.UTF8);
+ _items = JsonSerializer.Deserialize<T[]>(jsonString, _jsonOptions);
return;
}
catch (Exception ex)
@@ -61,7 +63,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void SaveList()
{
Directory.CreateDirectory(Path.GetDirectoryName(_dataPath));
- _jsonSerializer.SerializeToFile(_items, _dataPath);
+ var jsonString = JsonSerializer.Serialize(_items, _jsonOptions);
+ File.WriteAllText(_dataPath, jsonString);
}
public IReadOnlyList<T> GetAll()
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
index 194e4606d..da707fec6 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -9,8 +9,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo>
{
- public SeriesTimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
- : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
+ public SeriesTimerManager(ILogger logger, string dataPath)
+ : base(logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index dd479b7d1..1efa90e25 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -8,7 +8,6 @@ using System.Threading;
using Jellyfin.Data.Events;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
@@ -17,8 +16,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase);
- public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
- : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
+ public TimerManager(ILogger logger, string dataPath)
+ : base(logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 90e6cc966..7567ea312 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -9,16 +9,17 @@ using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.Listings
@@ -28,7 +29,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
private readonly ILogger<SchedulesDirect> _logger;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory;
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
private readonly IApplicationHost _appHost;
@@ -36,16 +36,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
private DateTime _lastErrorResponse;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
public SchedulesDirect(
ILogger<SchedulesDirect> logger,
- IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
IApplicationHost appHost,
ICryptoProvider cryptoProvider)
{
_logger = logger;
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_appHost = appHost;
_cryptoProvider = cryptoProvider;
@@ -104,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
};
- var requestString = _jsonSerializer.SerializeToString(requestList);
+ var requestString = JsonSerializer.Serialize(requestList, _jsonOptions);
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules");
@@ -112,7 +111,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(responseStream).ConfigureAwait(false);
+ var dailySchedules = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Day>>(responseStream, _jsonOptions).ConfigureAwait(false);
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
@@ -123,7 +122,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream).ConfigureAwait(false);
+ var programDetails = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream, _jsonOptions).ConfigureAwait(false);
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
var programIdsWithImages =
@@ -479,7 +478,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>(response).ConfigureAwait(false);
+ return await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ShowImages>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -508,7 +507,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(response).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Headends>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (root != null)
{
@@ -649,7 +648,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(stream).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Token>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (string.Equals(root.message, "OK", StringComparison.Ordinal))
{
_logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
@@ -705,7 +704,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
httpResponse.EnsureSuccessStatusCode();
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var response = httpResponse.Content;
- var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(stream).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Lineups>(stream, _jsonOptions).ConfigureAwait(false);
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
}
@@ -777,25 +776,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(stream).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Channel>(stream, _jsonOptions).ConfigureAwait(false);
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
_logger.LogInformation("Mapping Stations to Channel");
var allStations = root.stations ?? new List<ScheduleDirect.Station>();
var map = root.map;
- int len = map.Count;
- var array = new List<ChannelInfo>(len);
- for (int i = 0; i < len; i++)
+ var list = new List<ChannelInfo>(map.Count);
+ foreach (var channel in map)
{
- var channelNumber = GetChannelNumber(map[i]);
+ var channelNumber = GetChannelNumber(channel);
- var station = allStations.Find(item => string.Equals(item.stationID, map[i].stationID, StringComparison.OrdinalIgnoreCase));
+ var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase));
if (station == null)
{
station = new ScheduleDirect.Station
{
- stationID = map[i].stationID
+ stationID = channel.stationID
};
}
@@ -812,10 +810,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
channelInfo.ImageUrl = station.logo.URL;
}
- array[i] = channelInfo;
+ list.Add(channelInfo);
}
- return array;
+ return list;
}
private static string NormalizeName(string value)
diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json
index 5667bf337..a23037af8 100644
--- a/Emby.Server.Implementations/Localization/Core/bn.json
+++ b/Emby.Server.Implementations/Localization/Core/bn.json
@@ -14,8 +14,8 @@
"HeaderFavoriteArtists": "প্রিয় শিল্পীরা",
"HeaderFavoriteAlbums": "প্রিয় এলবামগুলো",
"HeaderContinueWatching": "দেখতে থাকুন",
- "HeaderAlbumArtists": "এলবাম শিল্পী",
- "Genres": "জেনার",
+ "HeaderAlbumArtists": "এলবাম শিল্পীবৃন্দ",
+ "Genres": "শৈলী",
"Folders": "ফোল্ডারগুলো",
"Favorites": "পছন্দসমূহ",
"FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে",
@@ -112,5 +112,10 @@
"TaskRefreshPeople": "পিপল রিফ্রেশ করুন",
"TaskCleanLogsDescription": "{0} দিনের বেশী পুরানো লগ ফাইলগুলি মুছে ফেলুন।",
"TaskCleanLogs": "লগ ডিরেক্টরি ক্লিন করুন",
- "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করুন।"
+ "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করুন।",
+ "Undefined": "অসঙ্গায়িত",
+ "Forced": "জোরকরে",
+ "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন",
+ "TaskCleanActivityLog": "কাজের ফাইল খালি করুন",
+ "Default": "প্রাথমিক"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json
index df68d3bbd..4cc2b378b 100644
--- a/Emby.Server.Implementations/Localization/Core/hi.json
+++ b/Emby.Server.Implementations/Localization/Core/hi.json
@@ -1,3 +1,30 @@
{
- "Albums": "आल्बुम्"
+ "Albums": "संग्रह",
+ "HeaderRecordingGroups": "रिकॉर्डिंग समूह",
+ "HeaderNextUp": "इसके बाद",
+ "HeaderLiveTV": "लाइव टीवी",
+ "HeaderFavoriteSongs": "पसंदीदा गीत",
+ "HeaderFavoriteShows": "पसंदीदा शोज",
+ "HeaderFavoriteEpisodes": "पसंदीदा एपिसोड्स",
+ "HeaderFavoriteArtists": "पसंदीदा कलाकारसमूह",
+ "HeaderFavoriteAlbums": "पसंदीदा एलबम्स",
+ "HeaderContinueWatching": "देखते रहिए",
+ "HeaderAlbumArtists": "एल्बम कलकरसमुह",
+ "Genres": "शैली",
+ "Forced": "बलपूर्वक",
+ "Folders": "फोल्डेरें",
+ "Favorites": "पसंदीदा",
+ "FailedLoginAttemptWithUserName": "{0} से लॉगिन असफल हुआ है",
+ "DeviceOnlineWithName": "{0} से संयोग हो गया है",
+ "DeviceOfflineWithName": "{0} से संयोग विच्छिन्न हो गया है",
+ "Default": "प्राथमिक",
+ "Collections": "संग्रह",
+ "ChapterNameValue": "अध्याय",
+ "Channels": "चैनल",
+ "CameraImageUploadedFrom": "कैमरा से एक नया चित्र अपलोड किया गया है",
+ "Books": "किताब",
+ "AuthenticationSucceededWithUserName": "सफलता से प्रमाणीकृत",
+ "Artists": "कलाकारों",
+ "Application": "एप्लिकेशन",
+ "AppDeviceValues": "एप: {0}, मशीन: {1}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json
index 9be91b724..9eb80b83b 100644
--- a/Emby.Server.Implementations/Localization/Core/hr.json
+++ b/Emby.Server.Implementations/Localization/Core/hr.json
@@ -115,5 +115,8 @@
"TasksChannelsCategory": "Internet kanali",
"TasksLibraryCategory": "Biblioteka",
"TaskCleanActivityLogDescription": "Briše zapise dnevnika aktivnosti starije od navedenog vremena.",
- "TaskCleanActivityLog": "Očisti dnevnik aktivnosti"
+ "TaskCleanActivityLog": "Očisti dnevnik aktivnosti",
+ "Undefined": "Nedefinirano",
+ "Forced": "Forsirani",
+ "Default": "Zadano"
}
diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json
index 47c0879be..7ce9822b6 100644
--- a/Emby.Server.Implementations/Localization/Core/kk.json
+++ b/Emby.Server.Implementations/Localization/Core/kk.json
@@ -108,5 +108,15 @@
"TasksLibraryCategory": "Tasyǵyshhana",
"TasksMaintenanceCategory": "Qyzmet kórsetý",
"Undefined": "Anyqtalmady",
- "Forced": "Májbúrli"
+ "Forced": "Májbúrli",
+ "TaskDownloadMissingSubtitlesDescription": "Metaderekter teńshelimi negіzіnde joq sýbtıtrlerdі Internetten іzdeıdі.",
+ "TaskRefreshChannelsDescription": "Internet-arnalar málimetterin jańartady.",
+ "TaskCleanTranscodeDescription": "Bіr kúnnen asqan qaıta kodtaý faıldaryn joıady.",
+ "TaskUpdatePluginsDescription": "Avtomatty túrde jańartýǵa teńshelgen plagınder úshin jańartýlardy júktep alady jáne ornatady.",
+ "TaskRefreshPeopleDescription": "Tasyǵyshhanadaǵy aktórler men rejısórler metaderekterіn jańartady.",
+ "TaskCleanLogsDescription": "{0} kúnnen asqan jurnal faıldaryn joıady.",
+ "TaskRefreshLibraryDescription": "Tasyǵyshhanadaǵy jańa faıldardy skanerleıdі jáne metaderekterdі jańartady.",
+ "TaskRefreshChapterImagesDescription": "Sahnalarǵa bólіngen beıneler úshіn nobaılar jasaıdy.",
+ "TaskCleanCacheDescription": "Júıede qajet emes keshtelgen faıldardy joıady.",
+ "TaskCleanActivityLogDescription": "Áreketter jurnalyndaǵy teńshelgen jasynan asqan jazbalaly joıady."
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 30aaf3a05..3f9e22106 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -5,7 +5,9 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text.Json;
using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
@@ -24,7 +26,6 @@ namespace Emby.Server.Implementations.Localization
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
private readonly IServerConfigurationManager _configurationManager;
- private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger<LocalizationManager> _logger;
private readonly Dictionary<string, Dictionary<string, ParentalRating>> _allParentalRatings =
@@ -35,19 +36,18 @@ namespace Emby.Server.Implementations.Localization
private List<CultureDto> _cultures;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+
/// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class.
/// </summary>
/// <param name="configurationManager">The configuration manager.</param>
- /// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
public LocalizationManager(
IServerConfigurationManager configurationManager,
- IJsonSerializer jsonSerializer,
ILogger<LocalizationManager> logger)
{
_configurationManager = configurationManager;
- _jsonSerializer = jsonSerializer;
_logger = logger;
}
@@ -179,8 +179,11 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc />
public IEnumerable<CountryInfo> GetCountries()
- => _jsonSerializer.DeserializeFromStream<IEnumerable<CountryInfo>>(
- _assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json"));
+ {
+ StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json"));
+
+ return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions);
+ }
/// <inheritdoc />
public IEnumerable<ParentalRating> GetParentalRatings()
@@ -344,7 +347,7 @@ namespace Emby.Server.Implementations.Localization
// If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
if (stream != null)
{
- var dict = await _jsonSerializer.DeserializeFromStreamAsync<Dictionary<string, string>>(stream).ConfigureAwait(false);
+ var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
foreach (var key in dict.Keys)
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index 3a9e28458..29440b64a 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -4,13 +4,15 @@ using System;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Progress;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
@@ -21,11 +23,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
public class ScheduledTaskWorker : IScheduledTaskWorker
{
- /// <summary>
- /// Gets or sets the json serializer.
- /// </summary>
- /// <value>The json serializer.</value>
- private readonly IJsonSerializer _jsonSerializer;
/// <summary>
/// Gets or sets the application paths.
@@ -70,12 +67,16 @@ namespace Emby.Server.Implementations.ScheduledTasks
private string _id;
/// <summary>
+ /// The options for the json Serializer.
+ /// </summary>
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+
+ /// <summary>
/// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
/// </summary>
/// <param name="scheduledTask">The scheduled task.</param>
/// <param name="applicationPaths">The application paths.</param>
/// <param name="taskManager">The task manager.</param>
- /// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="ArgumentNullException">
/// scheduledTask
@@ -88,7 +89,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// or
/// logger.
/// </exception>
- public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger)
+ public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger)
{
if (scheduledTask == null)
{
@@ -105,11 +106,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
throw new ArgumentNullException(nameof(taskManager));
}
- if (jsonSerializer == null)
- {
- throw new ArgumentNullException(nameof(jsonSerializer));
- }
-
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
@@ -118,7 +114,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
ScheduledTask = scheduledTask;
_applicationPaths = applicationPaths;
_taskManager = taskManager;
- _jsonSerializer = jsonSerializer;
_logger = logger;
InitTriggerEvents();
@@ -150,7 +145,15 @@ namespace Emby.Server.Implementations.ScheduledTasks
{
try
{
- _lastExecutionResult = _jsonSerializer.DeserializeFromFile<TaskResult>(path);
+ var jsonString = File.ReadAllText(path, Encoding.UTF8);
+ if (!string.IsNullOrWhiteSpace(jsonString))
+ {
+ _lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(jsonString, _jsonOptions);
+ }
+ else
+ {
+ _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path);
+ }
}
catch (Exception ex)
{
@@ -174,7 +177,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
lock (_lastExecutionResultSyncLock)
{
- _jsonSerializer.SerializeToFile(value, path);
+ using FileStream createStream = File.OpenWrite(path);
+ JsonSerializer.SerializeAsync(createStream, value, _jsonOptions);
}
}
}
@@ -537,7 +541,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
TaskTriggerInfo[] list = null;
if (File.Exists(path))
{
- list = _jsonSerializer.DeserializeFromFile<TaskTriggerInfo[]>(path);
+ var jsonString = File.ReadAllText(path, Encoding.UTF8);
+ list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(jsonString, _jsonOptions);
}
// Return defaults if file doesn't exist.
@@ -573,7 +578,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(triggers, path);
+ var json = JsonSerializer.Serialize(triggers, _jsonOptions);
+ File.WriteAllText(path, json, Encoding.UTF8);
}
/// <summary>
diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
index cfbf03ddc..af316e108 100644
--- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
@@ -7,7 +7,6 @@ using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
@@ -19,6 +18,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
public class TaskManager : ITaskManager
{
public event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting;
+
public event EventHandler<TaskCompletionEventArgs> TaskCompleted;
/// <summary>
@@ -33,7 +33,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue =
new ConcurrentQueue<Tuple<Type, TaskOptions>>();
- private readonly IJsonSerializer _jsonSerializer;
private readonly IApplicationPaths _applicationPaths;
private readonly ILogger<TaskManager> _logger;
@@ -41,15 +40,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// Initializes a new instance of the <see cref="TaskManager" /> class.
/// </summary>
/// <param name="applicationPaths">The application paths.</param>
- /// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
public TaskManager(
IApplicationPaths applicationPaths,
- IJsonSerializer jsonSerializer,
ILogger<TaskManager> logger)
{
_applicationPaths = applicationPaths;
- _jsonSerializer = jsonSerializer;
_logger = logger;
ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
@@ -196,7 +192,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="tasks">The tasks.</param>
public void AddTasks(IEnumerable<IScheduledTask> tasks)
{
- var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger));
+ var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _logger));
ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
}
diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs
deleted file mode 100644
index 5ec3a735a..000000000
--- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs
+++ /dev/null
@@ -1,281 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-using System.IO;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Serialization;
-
-namespace Emby.Server.Implementations.Serialization
-{
- /// <summary>
- /// Provides a wrapper around third party json serialization.
- /// </summary>
- public class JsonSerializer : IJsonSerializer
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="JsonSerializer" /> class.
- /// </summary>
- public JsonSerializer()
- {
- ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601;
- ServiceStack.Text.JsConfig.ExcludeTypeInfo = true;
- ServiceStack.Text.JsConfig.IncludeNullValues = false;
- ServiceStack.Text.JsConfig.AlwaysUseUtc = true;
- ServiceStack.Text.JsConfig.AssumeUtc = true;
-
- ServiceStack.Text.JsConfig<Guid>.SerializeFn = SerializeGuid;
- }
-
- /// <summary>
- /// Serializes to stream.
- /// </summary>
- /// <param name="obj">The obj.</param>
- /// <param name="stream">The stream.</param>
- /// <exception cref="ArgumentNullException">obj</exception>
- public void SerializeToStream(object obj, Stream stream)
- {
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
-
- if (stream == null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
-
- ServiceStack.Text.JsonSerializer.SerializeToStream(obj, obj.GetType(), stream);
- }
-
- /// <summary>
- /// Serializes to stream.
- /// </summary>
- /// <param name="obj">The obj.</param>
- /// <param name="stream">The stream.</param>
- /// <exception cref="ArgumentNullException">obj</exception>
- public void SerializeToStream<T>(T obj, Stream stream)
- {
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
-
- if (stream == null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
-
- ServiceStack.Text.JsonSerializer.SerializeToStream<T>(obj, stream);
- }
-
- /// <summary>
- /// Serializes to file.
- /// </summary>
- /// <param name="obj">The obj.</param>
- /// <param name="file">The file.</param>
- /// <exception cref="ArgumentNullException">obj</exception>
- public void SerializeToFile(object obj, string file)
- {
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
-
- if (string.IsNullOrEmpty(file))
- {
- throw new ArgumentNullException(nameof(file));
- }
-
- using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
- {
- SerializeToStream(obj, stream);
- }
- }
-
- private static Stream OpenFile(string path)
- {
- return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072);
- }
-
- /// <summary>
- /// Deserializes from file.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="file">The file.</param>
- /// <returns>System.Object.</returns>
- /// <exception cref="ArgumentNullException">type</exception>
- public object DeserializeFromFile(Type type, string file)
- {
- if (type == null)
- {
- throw new ArgumentNullException(nameof(type));
- }
-
- if (string.IsNullOrEmpty(file))
- {
- throw new ArgumentNullException(nameof(file));
- }
-
- using (var stream = OpenFile(file))
- {
- return DeserializeFromStream(stream, type);
- }
- }
-
- /// <summary>
- /// Deserializes from file.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="file">The file.</param>
- /// <returns>``0.</returns>
- /// <exception cref="ArgumentNullException">file</exception>
- public T DeserializeFromFile<T>(string file)
- where T : class
- {
- if (string.IsNullOrEmpty(file))
- {
- throw new ArgumentNullException(nameof(file));
- }
-
- using (var stream = OpenFile(file))
- {
- return DeserializeFromStream<T>(stream);
- }
- }
-
- /// <summary>
- /// Deserializes from stream.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="stream">The stream.</param>
- /// <returns>``0.</returns>
- /// <exception cref="ArgumentNullException">stream</exception>
- public T DeserializeFromStream<T>(Stream stream)
- {
- if (stream == null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
-
- return ServiceStack.Text.JsonSerializer.DeserializeFromStream<T>(stream);
- }
-
- public Task<T> DeserializeFromStreamAsync<T>(Stream stream)
- {
- if (stream == null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
-
- return ServiceStack.Text.JsonSerializer.DeserializeFromStreamAsync<T>(stream);
- }
-
- /// <summary>
- /// Deserializes from string.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="text">The text.</param>
- /// <returns>``0.</returns>
- /// <exception cref="ArgumentNullException">text</exception>
- public T DeserializeFromString<T>(string text)
- {
- if (string.IsNullOrEmpty(text))
- {
- throw new ArgumentNullException(nameof(text));
- }
-
- return ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(text);
- }
-
- /// <summary>
- /// Deserializes from stream.
- /// </summary>
- /// <param name="stream">The stream.</param>
- /// <param name="type">The type.</param>
- /// <returns>System.Object.</returns>
- /// <exception cref="ArgumentNullException">stream</exception>
- public object DeserializeFromStream(Stream stream, Type type)
- {
- if (stream == null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
-
- if (type == null)
- {
- throw new ArgumentNullException(nameof(type));
- }
-
- return ServiceStack.Text.JsonSerializer.DeserializeFromStream(type, stream);
- }
-
- public async Task<object> DeserializeFromStreamAsync(Stream stream, Type type)
- {
- if (stream == null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
-
- if (type == null)
- {
- throw new ArgumentNullException(nameof(type));
- }
-
- using (var reader = new StreamReader(stream))
- {
- var json = await reader.ReadToEndAsync().ConfigureAwait(false);
-
- return ServiceStack.Text.JsonSerializer.DeserializeFromString(json, type);
- }
- }
-
- private static string SerializeGuid(Guid guid)
- {
- if (guid.Equals(Guid.Empty))
- {
- return null;
- }
-
- return guid.ToString("N", CultureInfo.InvariantCulture);
- }
-
- /// <summary>
- /// Deserializes from string.
- /// </summary>
- /// <param name="json">The json.</param>
- /// <param name="type">The type.</param>
- /// <returns>System.Object.</returns>
- /// <exception cref="ArgumentNullException">json</exception>
- public object DeserializeFromString(string json, Type type)
- {
- if (string.IsNullOrEmpty(json))
- {
- throw new ArgumentNullException(nameof(json));
- }
-
- if (type == null)
- {
- throw new ArgumentNullException(nameof(type));
- }
-
- return ServiceStack.Text.JsonSerializer.DeserializeFromString(json, type);
- }
-
- /// <summary>
- /// Serializes to string.
- /// </summary>
- /// <param name="obj">The obj.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">obj</exception>
- public string SerializeToString(object obj)
- {
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
-
- return ServiceStack.Text.JsonSerializer.SerializeToString(obj, obj.GetType());
- }
- }
-}
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index 4fd9c2fbf..694d16ad9 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -41,18 +41,25 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Description xml returned.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
[HttpGet("{serverId}/description")]
[HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult GetDescriptionXml([FromRoute, Required] string serverId)
{
- var url = GetAbsoluteUri();
- var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
- var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
- return Ok(xml);
+ if (DlnaEntryPoint.Enabled)
+ {
+ var url = GetAbsoluteUri();
+ var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
+ var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
+ return Ok(xml);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -60,17 +67,24 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Dlna content directory returned.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
[HttpGet("{serverId}/ContentDirectory")]
[HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
[HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetContentDirectory([FromRoute, Required] string serverId)
{
- return Ok(_contentDirectory.GetServiceXml());
+ if (DlnaEntryPoint.Enabled)
+ {
+ return Ok(_contentDirectory.GetServiceXml());
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -78,17 +92,24 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Dlna media receiver registrar xml returned.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Dlna media receiver registrar xml.</returns>
[HttpGet("{serverId}/MediaReceiverRegistrar")]
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
{
- return Ok(_mediaReceiverRegistrar.GetServiceXml());
+ if (DlnaEntryPoint.Enabled)
+ {
+ return Ok(_mediaReceiverRegistrar.GetServiceXml());
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -96,17 +117,24 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Dlna media receiver registrar xml returned.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Dlna media receiver registrar xml.</returns>
[HttpGet("{serverId}/ConnectionManager")]
[HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
[HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetConnectionManager([FromRoute, Required] string serverId)
{
- return Ok(_connectionManager.GetServiceXml());
+ if (DlnaEntryPoint.Enabled)
+ {
+ return Ok(_connectionManager.GetServiceXml());
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -114,14 +142,21 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Request processed.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/ContentDirectory/Control")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
{
- return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
+ if (DlnaEntryPoint.Enabled)
+ {
+ return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -129,14 +164,21 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Request processed.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/ConnectionManager/Control")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
{
- return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
+ if (DlnaEntryPoint.Enabled)
+ {
+ return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -144,14 +186,21 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Request processed.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
{
- return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
+ if (DlnaEntryPoint.Enabled)
+ {
+ return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -159,17 +208,24 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Request processed.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Event subscription response.</returns>
[HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")]
[HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")]
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId)
{
- return ProcessEventRequest(_mediaReceiverRegistrar);
+ if (DlnaEntryPoint.Enabled)
+ {
+ return ProcessEventRequest(_mediaReceiverRegistrar);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -177,17 +233,24 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Request processed.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Event subscription response.</returns>
[HttpSubscribe("{serverId}/ContentDirectory/Events")]
[HttpUnsubscribe("{serverId}/ContentDirectory/Events")]
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId)
{
- return ProcessEventRequest(_contentDirectory);
+ if (DlnaEntryPoint.Enabled)
+ {
+ return ProcessEventRequest(_contentDirectory);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -195,17 +258,24 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <response code="200">Request processed.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Event subscription response.</returns>
[HttpSubscribe("{serverId}/ConnectionManager/Events")]
[HttpUnsubscribe("{serverId}/ConnectionManager/Events")]
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId)
{
- return ProcessEventRequest(_connectionManager);
+ if (DlnaEntryPoint.Enabled)
+ {
+ return ProcessEventRequest(_connectionManager);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -213,14 +283,24 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="serverId">Server UUID.</param>
/// <param name="fileName">The icon filename.</param>
+ /// <response code="200">Request processed.</response>
+ /// <response code="404">Not Found.</response>
+ /// <response code="503">DLNA is disabled.</response>
/// <returns>Icon stream.</returns>
[HttpGet("{serverId}/icons/{fileName}")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[ProducesImageFile]
public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
{
- return GetIconInternal(fileName);
+ if (DlnaEntryPoint.Enabled)
+ {
+ return GetIconInternal(fileName);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
/// <summary>
@@ -228,11 +308,22 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="fileName">The icon filename.</param>
/// <returns>Icon stream.</returns>
+ /// <response code="200">Request processed.</response>
+ /// <response code="404">Not Found.</response>
+ /// <response code="503">DLNA is disabled.</response>
[HttpGet("icons/{fileName}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[ProducesImageFile]
public ActionResult GetIcon([FromRoute, Required] string fileName)
{
- return GetIconInternal(fileName);
+ if (DlnaEntryPoint.Enabled)
+ {
+ return GetIconInternal(fileName);
+ }
+
+ return StatusCode(StatusCodes.Status503ServiceUnavailable);
}
private ActionResult GetIconInternal(string fileName)
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index 2a1da31c9..baa2e0636 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -259,24 +259,24 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? subtitleStreamIndex,
[FromQuery] int? maxAudioChannels,
[FromQuery] Guid? itemId,
- [FromBody] OpenLiveStreamDto openLiveStreamDto,
- [FromQuery] bool enableDirectPlay = true,
- [FromQuery] bool enableDirectStream = true)
+ [FromBody] OpenLiveStreamDto? openLiveStreamDto,
+ [FromQuery] bool? enableDirectPlay,
+ [FromQuery] bool? enableDirectStream)
{
var request = new LiveStreamRequest
{
- OpenToken = openToken,
- UserId = userId ?? Guid.Empty,
- PlaySessionId = playSessionId,
- MaxStreamingBitrate = maxStreamingBitrate,
- StartTimeTicks = startTimeTicks,
- AudioStreamIndex = audioStreamIndex,
- SubtitleStreamIndex = subtitleStreamIndex,
- MaxAudioChannels = maxAudioChannels,
- ItemId = itemId ?? Guid.Empty,
+ OpenToken = openToken ?? openLiveStreamDto?.OpenToken,
+ UserId = userId ?? openLiveStreamDto?.UserId ?? Guid.Empty,
+ PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId,
+ MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate,
+ StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks,
+ AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex,
+ SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex,
+ MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels,
+ ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty,
DeviceProfile = openLiveStreamDto?.DeviceProfile,
- EnableDirectPlay = enableDirectPlay,
- EnableDirectStream = enableDirectStream,
+ EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true,
+ EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true,
DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http }
};
return await _mediaInfoHelper.OpenMediaSource(Request, request).ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index 7784e8a11..e67a27ae3 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
+using System.Net;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
@@ -66,7 +67,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<SystemInfo> GetSystemInfo()
{
- return _appHost.GetSystemInfo(Request.HttpContext.Connection.RemoteIpAddress);
+ return _appHost.GetSystemInfo(Request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
}
/// <summary>
@@ -78,7 +79,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<PublicSystemInfo> GetPublicSystemInfo()
{
- return _appHost.GetPublicSystemInfo(Request.HttpContext.Connection.RemoteIpAddress);
+ return _appHost.GetPublicSystemInfo(Request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
}
/// <summary>
@@ -202,7 +203,7 @@ namespace Jellyfin.Api.Controllers
// For older files, assume fully static
var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite;
FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare);
- return File(stream, "text/plain");
+ return File(stream, "text/plain; charset=utf-8");
}
/// <summary>
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index b4f2817f7..f01f50cea 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -15,9 +15,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.1" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.6.3" />
diff --git a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs
index b0b3de855..704542326 100644
--- a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs
+++ b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs
@@ -11,6 +11,61 @@ namespace Jellyfin.Api.Models.MediaInfoDtos
public class OpenLiveStreamDto
{
/// <summary>
+ /// Gets or sets the open token.
+ /// </summary>
+ public string? OpenToken { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user id.
+ /// </summary>
+ public Guid? UserId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the play session id.
+ /// </summary>
+ public string? PlaySessionId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the max streaming bitrate.
+ /// </summary>
+ public int? MaxStreamingBitrate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start time in ticks.
+ /// </summary>
+ public long? StartTimeTicks { get; set; }
+
+ /// <summary>
+ /// Gets or sets the audio stream index.
+ /// </summary>
+ public int? AudioStreamIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the subtitle stream index.
+ /// </summary>
+ public int? SubtitleStreamIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the max audio channels.
+ /// </summary>
+ public int? MaxAudioChannels { get; set; }
+
+ /// <summary>
+ /// Gets or sets the item id.
+ /// </summary>
+ public Guid? ItemId { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable direct play.
+ /// </summary>
+ public bool? EnableDirectPlay { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enale direct stream.
+ /// </summary>
+ public bool? EnableDirectStream { get; set; }
+
+ /// <summary>
/// Gets or sets the device profile.
/// </summary>
public DeviceProfile? DeviceProfile { get; set; }
diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
index 0e94f87f6..e9f9aad57 100644
--- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
+++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Text.RegularExpressions;
using SkiaSharp;
namespace Jellyfin.Drawing.Skia
@@ -118,6 +119,16 @@ namespace Jellyfin.Drawing.Skia
};
canvas.DrawRect(0, 0, width, height, paintColor);
+ var typeFace = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
+
+ // use the system fallback to find a typeface for the given CJK character
+ var nonCjkPattern = @"[^\p{IsCJKUnifiedIdeographs}\p{IsCJKUnifiedIdeographsExtensionA}\p{IsKatakana}\p{IsHiragana}\p{IsHangulSyllables}\p{IsHangulJamo}]";
+ var filteredName = Regex.Replace(libraryName ?? string.Empty, nonCjkPattern, string.Empty);
+ if (!string.IsNullOrEmpty(filteredName))
+ {
+ typeFace = SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, filteredName[0]);
+ }
+
// draw library name
var textPaint = new SKPaint
{
@@ -125,7 +136,7 @@ namespace Jellyfin.Drawing.Skia
Style = SKPaintStyle.Fill,
TextSize = 112,
TextAlign = SKTextAlign.Center,
- Typeface = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright),
+ Typeface = typeFace,
IsAntialias = true
};
diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs
index 43f2f7add..60b899519 100644
--- a/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -784,7 +784,7 @@ namespace Jellyfin.Networking.Manager
}
else
{
- _logger.LogDebug("Invalid or unknown network {Token}.", token);
+ _logger.LogDebug("Invalid or unknown object {Token}.", token);
}
}
@@ -913,15 +913,6 @@ namespace Jellyfin.Networking.Manager
{
string[] lanAddresses = config.LocalNetworkAddresses;
- // TODO: remove when bug fixed: https://github.com/jellyfin/jellyfin-web/issues/1334
-
- if (lanAddresses.Length == 1 && lanAddresses[0].IndexOf(',', StringComparison.OrdinalIgnoreCase) != -1)
- {
- lanAddresses = lanAddresses[0].Split(',');
- }
-
- // TODO: end fix: https://github.com/jellyfin/jellyfin-web/issues/1334
-
// Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded.
if (config.IgnoreVirtualInterfaces)
{
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 97fb56ba1..bc000fd45 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -52,7 +52,6 @@
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
- <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
index dd005b7f4..f4040748d 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
@@ -8,7 +8,6 @@ using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Server.Implementations;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@@ -81,7 +80,8 @@ namespace Jellyfin.Server.Migrations.Routines
{ "unstable", ChromecastVersion.Unstable }
};
- var customDisplayPrefs = new HashSet<string>();
+ var displayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ var customDisplayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var dbFilePath = Path.Combine(_paths.DataPath, DbFilename);
using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null))
{
@@ -98,6 +98,15 @@ namespace Jellyfin.Server.Migrations.Routines
var itemId = new Guid(result[1].ToBlob());
var dtoUserId = new Guid(result[1].ToBlob());
+ var client = result[2].ToString();
+ var displayPreferencesKey = $"{dtoUserId}|{itemId}|{client}";
+ if (displayPrefs.Contains(displayPreferencesKey))
+ {
+ // Duplicate display preference.
+ continue;
+ }
+
+ displayPrefs.Add(displayPreferencesKey);
var existingUser = _userManager.GetUserById(dtoUserId);
if (existingUser == null)
{
@@ -110,7 +119,7 @@ namespace Jellyfin.Server.Migrations.Routines
: ChromecastVersion.Stable;
dto.CustomPrefs.Remove("chromecastVersion");
- var displayPreferences = new DisplayPreferences(dtoUserId, itemId, result[2].ToString())
+ var displayPreferences = new DisplayPreferences(dtoUserId, itemId, client)
{
IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null,
ShowBackdrop = dto.ShowBackdrop,
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index a1a7a3053..f05cdfe9b 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -598,7 +598,8 @@ namespace Jellyfin.Server
.WriteTo.Async(x => x.File(
Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
rollingInterval: RollingInterval.Day,
- outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}"))
+ outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}",
+ encoding: Encoding.UTF8))
.Enrich.FromLogContext()
.Enrich.WithThreadId()
.CreateLogger();
diff --git a/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs b/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs
new file mode 100644
index 000000000..73e3a0493
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ /// <summary>
+ /// Legacy DateTime converter.
+ /// Milliseconds aren't output if zero by default.
+ /// </summary>
+ public class JsonDateTimeConverter : JsonConverter<DateTime>
+ {
+ /// <inheritdoc />
+ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return reader.GetDateTime();
+ }
+
+ /// <inheritdoc />
+ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
+ {
+ if (value.Millisecond == 0)
+ {
+ // Remaining ticks value will be 0, manually format.
+ writer.WriteStringValue(value.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffZ", CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ writer.WriteStringValue(value);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs b/MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs
new file mode 100644
index 000000000..6d96d5496
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ /// <summary>
+ /// Converts a GUID object or value to/from JSON.
+ /// </summary>
+ public class JsonNullableGuidConverter : JsonConverter<Guid?>
+ {
+ /// <inheritdoc />
+ public override Guid? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var guidStr = reader.GetString();
+ return guidStr == null ? null : new Guid(guidStr);
+ }
+
+ /// <inheritdoc />
+ public override void Write(Utf8JsonWriter writer, Guid? value, JsonSerializerOptions options)
+ {
+ if (value == null || value == Guid.Empty)
+ {
+ writer.WriteNullValue();
+ }
+ else
+ {
+ writer.WriteStringValue(value.Value.ToString("N", CultureInfo.InvariantCulture));
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs
new file mode 100644
index 000000000..4fec2ea3f
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ /// <summary>
+ /// Converts a string <c>N/A</c> to <c>string.Empty</c>.
+ /// </summary>
+ public class JsonOmdbNotAvailableStringConverter : JsonConverter<string>
+ {
+ /// <inheritdoc />
+ public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ var str = reader.GetString();
+ if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ return str;
+ }
+
+ return JsonSerializer.Deserialize<string>(ref reader, options);
+ }
+
+ /// <inheritdoc />
+ public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
+ {
+ JsonSerializer.Serialize(value, options);
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStructConverter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStructConverter.cs
new file mode 100644
index 000000000..b9e67ce2d
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStructConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ /// <summary>
+ /// Converts a string <c>N/A</c> to <c>string.Empty</c>.
+ /// </summary>
+ /// <typeparam name="T">The resulting type.</typeparam>
+ public class JsonOmdbNotAvailableStructConverter<T> : JsonConverter<T?>
+ where T : struct
+ {
+ /// <inheritdoc />
+ public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ var str = reader.GetString();
+ if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+ }
+
+ return JsonSerializer.Deserialize<T>(ref reader, options);
+ }
+
+ /// <inheritdoc />
+ public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
+ {
+ JsonSerializer.Serialize(value, options);
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs
index b76edd2bc..2ef24a884 100644
--- a/MediaBrowser.Common/Json/JsonDefaults.cs
+++ b/MediaBrowser.Common/Json/JsonDefaults.cs
@@ -20,54 +20,70 @@ namespace MediaBrowser.Common.Json
public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\"";
/// <summary>
+ /// When changing these options, update
+ /// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+ /// -> AddJellyfinApi
+ /// -> AddJsonOptions.
+ /// </summary>
+ private static readonly JsonSerializerOptions _jsonSerializerOptions = new ()
+ {
+ ReadCommentHandling = JsonCommentHandling.Disallow,
+ WriteIndented = false,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ NumberHandling = JsonNumberHandling.AllowReadingFromString,
+ Converters =
+ {
+ new JsonGuidConverter(),
+ new JsonNullableGuidConverter(),
+ new JsonVersionConverter(),
+ new JsonStringEnumConverter(),
+ new JsonNullableStructConverterFactory(),
+ new JsonBoolNumberConverter(),
+ new JsonDateTimeConverter()
+ }
+ };
+
+ private static readonly JsonSerializerOptions _pascalCaseJsonSerializerOptions = new (_jsonSerializerOptions)
+ {
+ PropertyNamingPolicy = null
+ };
+
+ private static readonly JsonSerializerOptions _camelCaseJsonSerializerOptions = new (_jsonSerializerOptions)
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
+ /// <summary>
/// Gets the default <see cref="JsonSerializerOptions" /> options.
/// </summary>
/// <remarks>
- /// When changing these options, update
- /// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
- /// -> AddJellyfinApi
- /// -> AddJsonOptions.
+ /// The return value must not be modified.
+ /// If the defaults must be modified the author must use the copy constructor.
/// </remarks>
/// <returns>The default <see cref="JsonSerializerOptions" /> options.</returns>
public static JsonSerializerOptions GetOptions()
- {
- var options = new JsonSerializerOptions
- {
- ReadCommentHandling = JsonCommentHandling.Disallow,
- WriteIndented = false,
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- NumberHandling = JsonNumberHandling.AllowReadingFromString
- };
-
- options.Converters.Add(new JsonGuidConverter());
- options.Converters.Add(new JsonVersionConverter());
- options.Converters.Add(new JsonStringEnumConverter());
- options.Converters.Add(new JsonNullableStructConverterFactory());
- options.Converters.Add(new JsonBoolNumberConverter());
-
- return options;
- }
+ => _jsonSerializerOptions;
/// <summary>
/// Gets camelCase json options.
/// </summary>
+ /// <remarks>
+ /// The return value must not be modified.
+ /// If the defaults must be modified the author must use the copy constructor.
+ /// </remarks>
/// <returns>The camelCase <see cref="JsonSerializerOptions" /> options.</returns>
public static JsonSerializerOptions GetCamelCaseOptions()
- {
- var options = GetOptions();
- options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
- return options;
- }
+ => _camelCaseJsonSerializerOptions;
/// <summary>
/// Gets PascalCase json options.
/// </summary>
+ /// <remarks>
+ /// The return value must not be modified.
+ /// If the defaults must be modified the author must use the copy constructor.
+ /// </remarks>
/// <returns>The PascalCase <see cref="JsonSerializerOptions" /> options.</returns>
public static JsonSerializerOptions GetPascalCaseOptions()
- {
- var options = GetOptions();
- options.PropertyNamingPolicy = null;
- return options;
- }
+ => _pascalCaseJsonSerializerOptions;
}
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index be5e7f5b4..320e60dc6 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -14,6 +14,7 @@
</PropertyGroup>
<ItemGroup>
+ <FrameworkReference Include="Microsoft.AspNetCore.App"/>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
@@ -21,7 +22,6 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
- <PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index d25545a2f..c3b6af76e 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -4,9 +4,11 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -24,10 +26,9 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class CollectionFolder : Folder, ICollectionFolder
{
+ private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
public static IXmlSerializer XmlSerializer { get; set; }
- public static IJsonSerializer JsonSerializer { get; set; }
-
public static IServerApplicationHost ApplicationHost { get; set; }
public CollectionFolder()
@@ -122,7 +123,7 @@ namespace MediaBrowser.Controller.Entities
{
LibraryOptions[path] = options;
- var clone = JsonSerializer.DeserializeFromString<LibraryOptions>(JsonSerializer.SerializeToString(options));
+ var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.Serialize(options, _jsonOptions), _jsonOptions);
foreach (var mediaPath in clone.PathInfos)
{
if (!string.IsNullOrEmpty(mediaPath.Path))
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 73ede7c5a..efab87a38 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -1131,8 +1131,8 @@ namespace MediaBrowser.Controller.MediaEncoding
profile = Regex.Replace(profile, @"\s+", string.Empty);
// We only transcode to HEVC 8-bit for now, force Main Profile.
- if (profile.Contains("main 10", StringComparison.OrdinalIgnoreCase)
- || profile.Contains("main still", StringComparison.OrdinalIgnoreCase))
+ if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
+ || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
{
profile = "main";
}
@@ -1145,7 +1145,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
- && profile.Contains("high 10", StringComparison.OrdinalIgnoreCase))
+ && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
{
profile = "high";
}
@@ -1177,9 +1177,21 @@ namespace MediaBrowser.Controller.MediaEncoding
profile = "high";
}
+ if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
+ {
+ profile = "constrained_baseline";
+ }
+
+ if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
+ {
+ profile = "constrained_high";
+ }
+
// Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile.
if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
- && profile.Contains("main 10", StringComparison.OrdinalIgnoreCase))
+ && profile.Contains("main10", StringComparison.OrdinalIgnoreCase))
{
profile = "main";
}
@@ -1710,6 +1722,16 @@ namespace MediaBrowser.Controller.MediaEncoding
: transcoderChannelLimit.Value;
}
+ // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
+ // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices
+ if (isTranscodingAudio
+ && state.TranscodingType != TranscodingJobType.Progressive
+ && resultChannels.HasValue
+ && (resultChannels.Value > 2 && resultChannels.Value < 6 || resultChannels.Value == 7))
+ {
+ resultChannels = 2;
+ }
+
return resultChannels;
}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 7bb2a7d03..f8af499e4 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Include="BDInfo" Version="0.7.6.1" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
</ItemGroup>
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 0dbd51bdc..9fb978e9b 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -49,8 +49,6 @@ namespace MediaBrowser.Model.Configuration
new MetadataOptions
{
ItemType = "Series",
- DisabledMetadataFetchers = new[] { "TheMovieDb" },
- DisabledImageFetchers = new[] { "TheMovieDb" }
},
new MetadataOptions
{
@@ -69,13 +67,10 @@ namespace MediaBrowser.Model.Configuration
new MetadataOptions
{
ItemType = "Season",
- DisabledMetadataFetchers = new[] { "TheMovieDb" },
},
new MetadataOptions
{
ItemType = "Episode",
- DisabledMetadataFetchers = new[] { "The Open Movie Database", "TheMovieDb" },
- DisabledImageFetchers = new[] { "The Open Movie Database", "TheMovieDb" }
}
};
}
@@ -304,6 +299,18 @@ namespace MediaBrowser.Model.Configuration
public int MinResumeDurationSeconds { get; set; } = 300;
/// <summary>
+ /// Gets or sets the minimum minutes of a book that must be played in order for playstate to be updated.
+ /// </summary>
+ /// <value>The min resume in minutes.</value>
+ public int MinAudiobookResume { get; set; } = 5;
+
+ /// <summary>
+ /// Gets or sets the remaining minutes of a book that can be played while still saving playstate. If this percentage is crossed playstate will be reset to the beginning and the item will be marked watched.
+ /// </summary>
+ /// <value>The remaining time in minutes.</value>
+ public int MaxAudiobookResume { get; set; } = 5;
+
+ /// <summary>
/// Gets or sets the delay in seconds that we will wait after a file system change to try and discover what has been added/removed
/// Some delay is necessary with some items because their creation is not atomic. It involves the creation of several
/// different directories and files.
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 59c981000..431cf0baf 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -1033,9 +1033,9 @@ namespace MediaBrowser.Model.Dlna
{
_logger.LogInformation(
"Profile: {0}, No video direct play profiles found for {1} with codec {2}",
- profile.Name ?? "Unknown Profile",
- mediaSource.Path ?? "Unknown path",
- videoStream.Codec ?? "Unknown codec");
+ profile?.Name ?? "Unknown Profile",
+ mediaSource?.Path ?? "Unknown path",
+ videoStream?.Codec ?? "Unknown codec");
return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index b86187f9b..334fe8209 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -33,7 +33,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
- <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="5.0.0" />
diff --git a/MediaBrowser.Model/Serialization/IJsonSerializer.cs b/MediaBrowser.Model/Serialization/IJsonSerializer.cs
deleted file mode 100644
index 09b6ff9b5..000000000
--- a/MediaBrowser.Model/Serialization/IJsonSerializer.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Model.Serialization
-{
- public interface IJsonSerializer
- {
- /// <summary>
- /// Serializes to stream.
- /// </summary>
- /// <param name="obj">The obj.</param>
- /// <param name="stream">The stream.</param>
- /// <exception cref="ArgumentNullException">obj</exception>
- void SerializeToStream(object obj, Stream stream);
-
- /// <summary>
- /// Serializes to stream.
- /// </summary>
- /// <param name="obj">The obj.</param>
- /// <param name="stream">The stream.</param>
- /// <exception cref="ArgumentNullException">obj</exception>
- void SerializeToStream<T>(T obj, Stream stream);
-
- /// <summary>
- /// Serializes to file.
- /// </summary>
- /// <param name="obj">The obj.</param>
- /// <param name="file">The file.</param>
- /// <exception cref="ArgumentNullException">obj</exception>
- void SerializeToFile(object obj, string file);
-
- /// <summary>
- /// Deserializes from file.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="file">The file.</param>
- /// <returns>System.Object.</returns>
- /// <exception cref="ArgumentNullException">type</exception>
- object DeserializeFromFile(Type type, string file);
-
- /// <summary>
- /// Deserializes from file.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="file">The file.</param>
- /// <returns>``0.</returns>
- /// <exception cref="ArgumentNullException">file</exception>
- T DeserializeFromFile<T>(string file)
- where T : class;
-
- /// <summary>
- /// Deserializes from stream.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="stream">The stream.</param>
- /// <returns>``0.</returns>
- /// <exception cref="ArgumentNullException">stream</exception>
- T DeserializeFromStream<T>(Stream stream);
-
- /// <summary>
- /// Deserializes from string.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="text">The text.</param>
- /// <returns>``0.</returns>
- /// <exception cref="ArgumentNullException">text</exception>
- T DeserializeFromString<T>(string text);
-
- /// <summary>
- /// Deserializes from stream.
- /// </summary>
- /// <param name="stream">The stream.</param>
- /// <param name="type">The type.</param>
- /// <returns>System.Object.</returns>
- /// <exception cref="ArgumentNullException">stream</exception>
- object DeserializeFromStream(Stream stream, Type type);
-
- /// <summary>
- /// Deserializes from string.
- /// </summary>
- /// <param name="json">The json.</param>
- /// <param name="type">The type.</param>
- /// <returns>System.Object.</returns>
- /// <exception cref="ArgumentNullException">json</exception>
- object DeserializeFromString(string json, Type type);
-
- /// <summary>
- /// Serializes to string.
- /// </summary>
- /// <param name="obj">The obj.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">obj</exception>
- string SerializeToString(object obj);
-
- Task<object> DeserializeFromStreamAsync(Stream stream, Type type);
- Task<T> DeserializeFromStreamAsync<T>(Stream stream);
- }
-}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index accdea36e..071a149db 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -19,10 +19,10 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
<PackageReference Include="TMDbLib" Version="1.7.3-alpha" />
- <PackageReference Include="TvDbSharper" Version="3.2.2" />
</ItemGroup>
<PropertyGroup>
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
index 293087da7..cd9e47743 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
@@ -1,9 +1,12 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.IO;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -19,13 +22,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _json;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
- public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IJsonSerializer json)
+ public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
{
_config = config;
_httpClientFactory = httpClientFactory;
- _json = json;
}
/// <inheritdoc />
@@ -56,7 +58,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = AudioDbAlbumProvider.GetAlbumInfoPath(_config.ApplicationPaths, id);
- var obj = _json.DeserializeFromFile<AudioDbAlbumProvider.RootObject>(path);
+ await using FileStream jsonStream = File.OpenRead(path);
+ var obj = await JsonSerializer.DeserializeAsync<AudioDbAlbumProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.album != null && obj.album.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
index 97bba10ba..f463a3566 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
@@ -6,10 +6,12 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
@@ -27,16 +29,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _json;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
public static AudioDbAlbumProvider Current;
- public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json)
+ public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
{
_config = config;
_fileSystem = fileSystem;
_httpClientFactory = httpClientFactory;
- _json = json;
Current = this;
}
@@ -64,7 +65,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetAlbumInfoPath(_config.ApplicationPaths, id);
- var obj = _json.DeserializeFromFile<RootObject>(path);
+ await using FileStream jsonStream = File.OpenRead(path);
+ var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.album != null && obj.album.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
index d250acfa8..36700d191 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
@@ -1,9 +1,12 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.IO;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -19,12 +22,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _json;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
- public AudioDbArtistImageProvider(IServerConfigurationManager config, IJsonSerializer json, IHttpClientFactory httpClientFactory)
+ public AudioDbArtistImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
{
_config = config;
- _json = json;
_httpClientFactory = httpClientFactory;
}
@@ -58,7 +60,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id);
- var obj = _json.DeserializeFromFile<AudioDbArtistProvider.RootObject>(path);
+ await using FileStream jsonStream = File.OpenRead(path);
+ var obj = await JsonSerializer.DeserializeAsync<AudioDbArtistProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.artists != null && obj.artists.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
index a2a03e1f9..7a15adb8e 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
@@ -5,10 +5,12 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
@@ -29,14 +31,13 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _json;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
- public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json)
+ public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
{
_config = config;
_fileSystem = fileSystem;
_httpClientFactory = httpClientFactory;
- _json = json;
Current = this;
}
@@ -65,7 +66,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetArtistInfoPath(_config.ApplicationPaths, id);
- var obj = _json.DeserializeFromFile<RootObject>(path);
+ await using FileStream jsonStream = File.OpenRead(path);
+ var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.artists != null && obj.artists.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index bfc840ea5..24ef80a35 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -12,13 +12,11 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
{
- private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory;
private readonly OmdbItemProvider _itemProvider;
private readonly IFileSystem _fileSystem;
@@ -26,19 +24,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb
private readonly IApplicationHost _appHost;
public OmdbEpisodeProvider(
- IJsonSerializer jsonSerializer,
IApplicationHost appHost,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager)
{
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
- _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClientFactory, libraryManager, fileSystem, configurationManager);
+ _itemProvider = new OmdbItemProvider(_appHost, httpClientFactory, libraryManager, fileSystem, configurationManager);
}
// After TheTvDb
@@ -69,7 +65,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
{
if (info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue)
{
- result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager)
+ result.HasMetadata = await new OmdbProvider(_httpClientFactory, _fileSystem, _appHost, _configurationManager)
.FetchEpisodeData(result, info.IndexNumber.Value, info.ParentIndexNumber.Value, info.GetProviderId(MetadataProvider.Imdb), seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
index 8f4240dc1..df67aff31 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
@@ -1,8 +1,8 @@
#pragma warning disable CS1591
using System.Collections.Generic;
-using System.Net.Http;
using System.Globalization;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
@@ -15,21 +15,18 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IApplicationHost _appHost;
- public OmdbImageProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
+ public OmdbImageProvider(IApplicationHost appHost, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
{
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
@@ -56,7 +53,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var list = new List<RemoteImageInfo>();
- var provider = new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager);
+ var provider = new OmdbProvider(_httpClientFactory, _fileSystem, _appHost, _configurationManager);
if (!string.IsNullOrWhiteSpace(imdbId))
{
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 43d8af75f..71d551063 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -6,9 +6,12 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Common.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -19,34 +22,35 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbItemProvider : IRemoteMetadataProvider<Series, SeriesInfo>,
IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder
{
- private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IApplicationHost _appHost;
+ private readonly JsonSerializerOptions _jsonOptions;
public OmdbItemProvider(
- IJsonSerializer jsonSerializer,
IApplicationHost appHost,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager)
{
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
+
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions());
+ _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
+ _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStructConverter<int>());
}
public string Name => "The Open Movie Database";
@@ -138,7 +142,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
if (isSearch)
{
- var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync<SearchResultList>(stream).ConfigureAwait(false);
+ var searchResultList = await JsonSerializer.DeserializeAsync<SearchResultList>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (searchResultList != null && searchResultList.Search != null)
{
resultList.AddRange(searchResultList.Search);
@@ -146,7 +150,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
else
{
- var result = await _jsonSerializer.DeserializeFromStreamAsync<SearchResult>(stream).ConfigureAwait(false);
+ var result = await JsonSerializer.DeserializeAsync<SearchResult>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (string.Equals(result.Response, "true", StringComparison.OrdinalIgnoreCase))
{
resultList.Add(result);
@@ -221,7 +225,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
result.HasMetadata = true;
- await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ await new OmdbProvider(_httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
@@ -253,7 +257,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
result.HasMetadata = true;
- await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ await new OmdbProvider(_httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index e6c605072..2372e3183 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -7,35 +7,41 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Common.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbProvider
{
- private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IApplicationHost _appHost;
+ private readonly JsonSerializerOptions _jsonOptions;
- public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
+ public OmdbProvider(IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
{
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
+
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions());
+ _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
+ _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStructConverter<int>());
}
public async Task Fetch<T>(MetadataResult<T> itemResult, string imdbId, string language, string country, CancellationToken cancellationToken)
@@ -220,7 +226,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
}
- var result = _jsonSerializer.DeserializeFromString<RootObject>(resultString);
+ var result = JsonSerializer.Deserialize<RootObject>(resultString, _jsonOptions);
return result;
}
@@ -239,7 +245,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
}
- var result = _jsonSerializer.DeserializeFromString<SeasonRootObject>(resultString);
+ var result = JsonSerializer.Deserialize<SeasonRootObject>(resultString, _jsonOptions);
return result;
}
@@ -297,11 +303,10 @@ namespace MediaBrowser.Providers.Plugins.Omdb
"i={0}&plot=short&tomatoes=true&r=json",
imdbParam));
- using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false);
+ var rootObject = await GetDeserializedOmdbResponse<RootObject>(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(rootObject, path);
+ await using FileStream jsonFileStream = File.OpenWrite(path);
+ await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false);
return path;
}
@@ -335,15 +340,22 @@ namespace MediaBrowser.Providers.Plugins.Omdb
imdbParam,
seasonId));
- using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false);
+ var rootObject = await GetDeserializedOmdbResponse<SeasonRootObject>(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(rootObject, path);
+ await using FileStream jsonFileStream = File.OpenWrite(path);
+ await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false);
return path;
}
+ public async Task<T> GetDeserializedOmdbResponse<T>(HttpClient httpClient, string url, CancellationToken cancellationToken)
+ {
+ using var response = await GetOmdbResponse(httpClient, url, cancellationToken).ConfigureAwait(false);
+ await using Stream content = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+
+ return await JsonSerializer.DeserializeAsync<T>(content, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ }
+
public static Task<HttpResponseMessage> GetOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken)
{
return httpClient.GetAsync(url, cancellationToken);
@@ -465,7 +477,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public string seriesID { get; set; }
- public int Season { get; set; }
+ public int? Season { get; set; }
public int? totalSeasons { get; set; }
@@ -526,7 +538,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public string Response { get; set; }
- public int Episode { get; set; }
+ public int? Episode { get; set; }
public float? GetRottenTomatoScore()
{
diff --git a/MediaBrowser.Providers/Properties/AssemblyInfo.cs b/MediaBrowser.Providers/Properties/AssemblyInfo.cs
index f1c46899c..fe4749c79 100644
--- a/MediaBrowser.Providers/Properties/AssemblyInfo.cs
+++ b/MediaBrowser.Providers/Properties/AssemblyInfo.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Resources;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
@@ -14,6 +15,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
+[assembly: InternalsVisibleTo("Jellyfin.Common.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
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index af4684f56..19c5612c0 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -29,6 +29,7 @@
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" />
+ <ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
new file mode 100644
index 000000000..efc0c4af9
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Globalization;
+using System.Text.Json;
+using MediaBrowser.Common.Json.Converters;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Json
+{
+ public class JsonNullableGuidConverterTests
+ {
+ private readonly JsonSerializerOptions _options;
+
+ public JsonNullableGuidConverterTests()
+ {
+ _options = new JsonSerializerOptions();
+ _options.Converters.Add(new JsonNullableGuidConverter());
+ }
+
+ [Fact]
+ public void Deserialize_Valid_Success()
+ {
+ Guid? value = JsonSerializer.Deserialize<Guid?>(@"""a852a27afe324084ae66db579ee3ee18""", _options);
+ Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value);
+ }
+
+ [Fact]
+ public void Deserialize_ValidDashed_Success()
+ {
+ Guid? value = JsonSerializer.Deserialize<Guid?>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options);
+ Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value);
+ }
+
+ [Fact]
+ public void Roundtrip_Valid_Success()
+ {
+ Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18");
+ string value = JsonSerializer.Serialize(guid, _options);
+ Assert.Equal(guid, JsonSerializer.Deserialize<Guid?>(value, _options));
+ }
+
+ [Fact]
+ public void Deserialize_Null_EmptyGuid()
+ {
+ Assert.Null(JsonSerializer.Deserialize<Guid?>("null", _options));
+ }
+
+ [Fact]
+ public void Serialize_EmptyGuid_EmptyGuid()
+ {
+ Assert.Equal("null", JsonSerializer.Serialize((Guid?)Guid.Empty, _options));
+ }
+
+ [Fact]
+ public void Serialize_Valid_NoDash_Success()
+ {
+ var guid = (Guid?)new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
+ var str = JsonSerializer.Serialize(guid, _options);
+ Assert.Equal($"\"{guid:N}\"", str);
+ }
+
+ [Fact]
+ public void Serialize_Nullable_Success()
+ {
+ Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
+ var str = JsonSerializer.Serialize(guid, _options);
+ Assert.Equal($"\"{guid:N}\"", str);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs
new file mode 100644
index 000000000..6f85fe092
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs
@@ -0,0 +1,68 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using MediaBrowser.Common.Json.Converters;
+using MediaBrowser.Providers.Plugins.Omdb;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Json
+{
+ public class JsonOmdbConverterTests
+ {
+ private readonly JsonSerializerOptions _options;
+
+ public JsonOmdbConverterTests()
+ {
+ _options = new JsonSerializerOptions();
+ _options.Converters.Add(new JsonOmdbNotAvailableStringConverter());
+ _options.Converters.Add(new JsonOmdbNotAvailableStructConverter<int>());
+ _options.NumberHandling = JsonNumberHandling.AllowReadingFromString;
+ }
+
+ [Fact]
+ public void Deserialize_Omdb_Response_Not_Available_Success()
+ {
+ const string Input = "{\"Title\":\"Chapter 1\",\"Year\":\"2013\",\"Rated\":\"TV-MA\",\"Released\":\"01 Feb 2013\",\"Season\":\"N/A\",\"Episode\":\"N/A\",\"Runtime\":\"55 min\",\"Genre\":\"Drama\",\"Director\":\"David Fincher\",\"Writer\":\"Michael Dobbs (based on the novels by), Andrew Davies (based on the mini-series by), Beau Willimon (created for television by), Beau Willimon, Sam Forman (staff writer)\",\"Actors\":\"Kevin Spacey, Robin Wright, Kate Mara, Corey Stoll\",\"Plot\":\"Congressman Francis Underwood has been declined the chair for Secretary of State. He's now gathering his own team to plot his revenge. Zoe Barnes, a reporter for the Washington Herald, will do anything to get her big break.\",\"Language\":\"English\",\"Country\":\"USA\",\"Awards\":\"N/A\",\"Poster\":\"https://m.media-amazon.com/images/M/MV5BMTY5MTU4NDQzNV5BMl5BanBnXkFtZTgwMzk2ODcxMzE@._V1_SX300.jpg\",\"Ratings\":[{\"Source\":\"Internet Movie Database\",\"Value\":\"8.7/10\"}],\"Metascore\":\"N/A\",\"imdbRating\":\"8.7\",\"imdbVotes\":\"6736\",\"imdbID\":\"tt2161930\",\"seriesID\":\"N/A\",\"Type\":\"episode\",\"Response\":\"True\"}";
+ var seasonRootObject = JsonSerializer.Deserialize<OmdbProvider.RootObject>(Input, _options);
+ Assert.NotNull(seasonRootObject);
+ Assert.Null(seasonRootObject?.Awards);
+ Assert.Null(seasonRootObject?.Episode);
+ Assert.Null(seasonRootObject?.Metascore);
+ }
+
+ [Theory]
+ [InlineData("\"N/A\"")]
+ [InlineData("null")]
+ public void Deserialization_To_Nullable_Int_Shoud_Be_Null(string input)
+ {
+ var result = JsonSerializer.Deserialize<int?>(input, _options);
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData("\"N/A\"")]
+ [InlineData("null")]
+ public void Deserialization_To_Nullable_String_Shoud_Be_Null(string input)
+ {
+ var result = JsonSerializer.Deserialize<string?>(input, _options);
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData("\"8\"", 8)]
+ [InlineData("8", 8)]
+ public void Deserialize_Int_Success(string input, int expected)
+ {
+ var result = JsonSerializer.Deserialize<int>(input, _options);
+ Assert.Equal(result, expected);
+ }
+
+ [Fact]
+ public void Deserialize_Normal_String_Success()
+ {
+ const string Input = "\"Jellyfin\"";
+ const string Expected = "Jellyfin";
+ var result = JsonSerializer.Deserialize<string>(Input, _options);
+ Assert.Equal(Expected, result);
+ }
+ }
+}