Merge remote-tracking branch 'upstream/master' into bye-tvdb
This commit is contained in:
commit
4b15284324
|
@ -7,7 +7,7 @@ parameters:
|
|||
default: "ubuntu-latest"
|
||||
- name: DotNetSdkVersion
|
||||
type: string
|
||||
default: 3.1.100
|
||||
default: 5.0.100
|
||||
|
||||
jobs:
|
||||
- job: CompatibilityCheck
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
parameters:
|
||||
LinuxImage: 'ubuntu-latest'
|
||||
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
DotNetSdkVersion: 3.1.100
|
||||
DotNetSdkVersion: 5.0.100
|
||||
|
||||
jobs:
|
||||
- job: Build
|
||||
|
|
|
@ -10,7 +10,7 @@ parameters:
|
|||
default: "tests/**/*Tests.csproj"
|
||||
- name: DotNetSdkVersion
|
||||
type: string
|
||||
default: 3.1.100
|
||||
default: 5.0.100
|
||||
|
||||
jobs:
|
||||
- job: Test
|
||||
|
@ -94,5 +94,5 @@ jobs:
|
|||
displayName: 'Publish OpenAPI Artifact'
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
inputs:
|
||||
targetPath: "tests/Jellyfin.Api.Tests/bin/Release/netcoreapp3.1/openapi.json"
|
||||
targetPath: "tests/Jellyfin.Api.Tests/bin/Release/net5.0/openapi.json"
|
||||
artifactName: 'OpenAPI Spec'
|
||||
|
|
|
@ -6,7 +6,7 @@ variables:
|
|||
- name: RestoreBuildProjects
|
||||
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
- name: DotNetSdkVersion
|
||||
value: 3.1.100
|
||||
value: 5.0.100
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
ARG DOTNET_VERSION=3.1
|
||||
ARG DOTNET_VERSION=5.0
|
||||
|
||||
FROM node:alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=master
|
||||
|
@ -8,7 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||
&& yarn install \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.1
|
||||
ARG DOTNET_VERSION=5.0
|
||||
|
||||
|
||||
FROM node:alpine as web-builder
|
||||
|
@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||
&& mv dist /dist
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.1
|
||||
ARG DOTNET_VERSION=5.0
|
||||
|
||||
|
||||
FROM node:alpine as web-builder
|
||||
|
@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||
&& mv dist /dist
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace DvdLib.Ifo
|
|||
continue;
|
||||
}
|
||||
|
||||
var nums = ifo.Name.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var nums = ifo.Name.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
|
||||
{
|
||||
ReadVTS(ifoNumber, ifo.FullName);
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace Emby.Dlna.Didl
|
|||
{
|
||||
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
_fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public bool Contains(string field)
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace Emby.Dlna.Eventing
|
|||
if (!string.IsNullOrEmpty(header))
|
||||
{
|
||||
// Starts with SECOND-
|
||||
header = header.Split('-').Last();
|
||||
header = header.Split('-')[^1];
|
||||
|
||||
if (int.TryParse(header, NumberStyles.Integer, _usCulture, out var val))
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace Emby.Notifications
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void OnAppHostHasPendingRestartChanged(object sender, EventArgs e)
|
||||
private async void OnAppHostHasPendingRestartChanged(object? sender, EventArgs e)
|
||||
{
|
||||
var type = NotificationType.ServerRestartRequired.ToString();
|
||||
|
||||
|
@ -99,7 +99,7 @@ namespace Emby.Notifications
|
|||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnActivityManagerEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
private async void OnActivityManagerEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
{
|
||||
var entry = e.Argument;
|
||||
|
||||
|
@ -132,7 +132,7 @@ namespace Emby.Notifications
|
|||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
private async void OnAppHostHasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
private async void OnAppHostHasUpdateAvailableChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (!_appHost.HasUpdateAvailable)
|
||||
{
|
||||
|
@ -151,7 +151,7 @@ namespace Emby.Notifications
|
|||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnLibraryManagerItemAdded(object sender, ItemChangeEventArgs e)
|
||||
private void OnLibraryManagerItemAdded(object? sender, ItemChangeEventArgs e)
|
||||
{
|
||||
if (!FilterItem(e.Item))
|
||||
{
|
||||
|
@ -197,7 +197,7 @@ namespace Emby.Notifications
|
|||
return item.SourceType == SourceType.Library;
|
||||
}
|
||||
|
||||
private async void LibraryUpdateTimerCallback(object state)
|
||||
private async void LibraryUpdateTimerCallback(object? state)
|
||||
{
|
||||
List<BaseItem> items;
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -133,6 +133,33 @@ namespace Emby.Server.Implementations.AppBase
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually pre-loads a factory so that it is available pre system initialisation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Class to register.</typeparam>
|
||||
public virtual void RegisterConfiguration<T>()
|
||||
where T : IConfigurationFactory
|
||||
{
|
||||
IConfigurationFactory factory = Activator.CreateInstance<T>();
|
||||
|
||||
if (_configurationFactories == null)
|
||||
{
|
||||
_configurationFactories = new[] { factory };
|
||||
}
|
||||
else
|
||||
{
|
||||
var oldLen = _configurationFactories.Length;
|
||||
var arr = new IConfigurationFactory[oldLen + 1];
|
||||
_configurationFactories.CopyTo(arr, 0);
|
||||
arr[oldLen] = factory;
|
||||
_configurationFactories = arr;
|
||||
}
|
||||
|
||||
_configurationStores = _configurationFactories
|
||||
.SelectMany(i => i.GetConfigurations())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds parts.
|
||||
/// </summary>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace Emby.Server.Implementations.AppBase
|
||||
|
@ -35,7 +36,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
configuration = Activator.CreateInstance(type);
|
||||
configuration = Activator.CreateInstance(type) ?? throw new ArgumentException($"Provided path ({type}) is not valid.", nameof(type));
|
||||
}
|
||||
|
||||
using var stream = new MemoryStream(buffer?.Length ?? 0);
|
||||
|
@ -48,8 +49,9 @@ namespace Emby.Server.Implementations.AppBase
|
|||
// If the file didn't exist before, or if something has changed, re-save
|
||||
if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path));
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
// Save it after load in case we got new items
|
||||
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
|
|
|
@ -125,7 +125,6 @@ namespace Emby.Server.Implementations
|
|||
private IMediaEncoder _mediaEncoder;
|
||||
private ISessionManager _sessionManager;
|
||||
private IHttpClientFactory _httpClientFactory;
|
||||
|
||||
private string[] _urlPrefixes;
|
||||
|
||||
/// <summary>
|
||||
|
@ -496,24 +495,11 @@ namespace Emby.Server.Implementations
|
|||
HttpsPort = ServerConfiguration.DefaultHttpsPort;
|
||||
}
|
||||
|
||||
if (Plugins != null)
|
||||
{
|
||||
var pluginBuilder = new StringBuilder();
|
||||
|
||||
foreach (var plugin in Plugins)
|
||||
{
|
||||
pluginBuilder.Append(plugin.Name)
|
||||
.Append(' ')
|
||||
.Append(plugin.Version)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
|
||||
}
|
||||
|
||||
DiscoverTypes();
|
||||
|
||||
RegisterServices();
|
||||
|
||||
RegisterPluginServices();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -777,10 +763,24 @@ namespace Emby.Server.Implementations
|
|||
|
||||
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||
_plugins = GetExports<IPlugin>()
|
||||
.Select(LoadPlugin)
|
||||
.Where(i => i != null)
|
||||
.ToArray();
|
||||
|
||||
if (Plugins != null)
|
||||
{
|
||||
var pluginBuilder = new StringBuilder();
|
||||
|
||||
foreach (var plugin in Plugins)
|
||||
{
|
||||
pluginBuilder.Append(plugin.Name)
|
||||
.Append(' ')
|
||||
.Append(plugin.Version)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
|
||||
}
|
||||
|
||||
_urlPrefixes = GetUrlPrefixes().ToArray();
|
||||
|
||||
Resolve<ILibraryManager>().AddParts(
|
||||
|
@ -810,21 +810,6 @@ namespace Emby.Server.Implementations
|
|||
Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
|
||||
}
|
||||
|
||||
private IPlugin LoadPlugin(IPlugin plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
plugin.RegisterServices(ServiceCollection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading plugin {PluginName}", plugin.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discovers the types.
|
||||
/// </summary>
|
||||
|
@ -835,6 +820,22 @@ namespace Emby.Server.Implementations
|
|||
_allConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray();
|
||||
}
|
||||
|
||||
private void RegisterPluginServices()
|
||||
{
|
||||
foreach (var pluginServiceRegistrator in GetExportTypes<IPluginServiceRegistrator>())
|
||||
{
|
||||
try
|
||||
{
|
||||
var instance = (IPluginServiceRegistrator)Activator.CreateInstance(pluginServiceRegistrator);
|
||||
instance.RegisterServices(ServiceCollection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error registering plugin services from {Assembly}.", pluginServiceRegistrator.Assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GetTypes(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
foreach (var ass in assemblies)
|
||||
|
@ -994,6 +995,12 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
var minimumVersion = new Version(0, 0, 0, 1);
|
||||
var versions = new List<LocalPlugin>();
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
// Plugin path doesn't exist, don't try to enumerate subfolders.
|
||||
return Enumerable.Empty<LocalPlugin>();
|
||||
}
|
||||
|
||||
var directories = Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly);
|
||||
|
||||
foreach (var dir in directories)
|
||||
|
@ -1024,7 +1031,7 @@ namespace Emby.Server.Implementations
|
|||
else
|
||||
{
|
||||
// No metafile, so lets see if the folder is versioned.
|
||||
metafile = dir.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)[^1];
|
||||
metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1];
|
||||
|
||||
int versionIndex = dir.LastIndexOf('_');
|
||||
if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version parsedVersion))
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using static MediaBrowser.Common.Cryptography.Constants;
|
||||
|
||||
|
@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
||||
}
|
||||
|
||||
using var h = HashAlgorithm.Create(hashMethod);
|
||||
using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException($"Unknown hash method: {hashMethod}.");
|
||||
if (salt.Length == 0)
|
||||
{
|
||||
return h.ComputeHash(bytes);
|
||||
|
|
|
@ -1007,7 +1007,7 @@ namespace Emby.Server.Implementations.Data
|
|||
return;
|
||||
}
|
||||
|
||||
var parts = value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
|
@ -1057,7 +1057,7 @@ namespace Emby.Server.Implementations.Data
|
|||
return;
|
||||
}
|
||||
|
||||
var parts = value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries);
|
||||
var list = new List<ItemImageInfo>();
|
||||
foreach (var part in parts)
|
||||
{
|
||||
|
@ -1096,7 +1096,7 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
||||
{
|
||||
var parts = value.Split(new[] { '*' }, StringSplitOptions.None);
|
||||
var parts = value.Split('*', StringSplitOptions.None);
|
||||
|
||||
if (parts.Length < 3)
|
||||
{
|
||||
|
@ -1532,7 +1532,7 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
if (!reader.IsDBNull(index))
|
||||
{
|
||||
item.Genres = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
item.Genres = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
index++;
|
||||
|
@ -1593,7 +1593,7 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
IEnumerable<MetadataField> GetLockedFields(string s)
|
||||
{
|
||||
foreach (var i in s.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
foreach (var i in s.Split('|', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (Enum.TryParse(i, true, out MetadataField parsedValue))
|
||||
{
|
||||
|
@ -1612,7 +1612,7 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
if (!reader.IsDBNull(index))
|
||||
{
|
||||
item.Studios = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
item.Studios = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
index++;
|
||||
|
@ -1622,7 +1622,7 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
if (!reader.IsDBNull(index))
|
||||
{
|
||||
item.Tags = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
item.Tags = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
index++;
|
||||
|
@ -1636,7 +1636,7 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
IEnumerable<TrailerType> GetTrailerTypes(string s)
|
||||
{
|
||||
foreach (var i in s.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
foreach (var i in s.Split('|', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (Enum.TryParse(i, true, out TrailerType parsedValue))
|
||||
{
|
||||
|
@ -1811,7 +1811,7 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
if (!reader.IsDBNull(index))
|
||||
{
|
||||
item.ProductionLocations = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
|
||||
item.ProductionLocations = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries).ToArray();
|
||||
}
|
||||
|
||||
index++;
|
||||
|
@ -1848,14 +1848,14 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
if (item is IHasArtist hasArtists && !reader.IsDBNull(index))
|
||||
{
|
||||
hasArtists.Artists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
hasArtists.Artists = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
if (item is IHasAlbumArtist hasAlbumArtists && !reader.IsDBNull(index))
|
||||
{
|
||||
hasAlbumArtists.AlbumArtists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
index++;
|
||||
|
@ -5611,7 +5611,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
return counts;
|
||||
}
|
||||
|
||||
var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
var allTypes = typeString.Split('|', StringSplitOptions.RemoveEmptyEntries)
|
||||
.ToLookup(x => x);
|
||||
|
||||
foreach (var type in allTypes)
|
||||
|
|
|
@ -275,7 +275,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
continue;
|
||||
}
|
||||
|
||||
var containers = container.Split(new[] { ',' });
|
||||
var containers = container.Split(',');
|
||||
if (containers.Length < 2)
|
||||
{
|
||||
continue;
|
||||
|
|
|
@ -32,13 +32,13 @@
|
|||
<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="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.0" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.10.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.26.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="DotNet.Glob" Version="3.1.0" />
|
||||
|
@ -49,10 +49,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
|
||||
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
|
||||
<NoWarn>AD0001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
|
|
|
@ -245,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
return null;
|
||||
}
|
||||
|
||||
var parts = authorizationHeader.Split(new[] { ' ' }, 2);
|
||||
var parts = authorizationHeader.Split(' ', 2);
|
||||
|
||||
// There should be at least to parts
|
||||
if (parts.Length != 2)
|
||||
|
@ -269,11 +269,11 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
|
||||
foreach (var item in parts)
|
||||
{
|
||||
var param = item.Trim().Split(new[] { '=' }, 2);
|
||||
var param = item.Trim().Split('=', 2);
|
||||
|
||||
if (param.Length == 2)
|
||||
{
|
||||
var value = NormalizeValue(param[1].Trim(new[] { '"' }));
|
||||
var value = NormalizeValue(param[1].Trim('"'));
|
||||
result[param[0]] = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2705,7 +2705,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
var videos = videoListResolver.Resolve(fileSystemChildren);
|
||||
|
||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files.First().Path, StringComparison.OrdinalIgnoreCase));
|
||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (currentVideo != null)
|
||||
{
|
||||
|
|
|
@ -849,7 +849,7 @@ namespace Emby.Server.Implementations.Library
|
|||
throw new ArgumentException("Key can't be empty.", nameof(key));
|
||||
}
|
||||
|
||||
var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2);
|
||||
var keys = key.Split(LiveStreamIdDelimeter, 2);
|
||||
|
||||
var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
|
|
|
@ -201,7 +201,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
continue;
|
||||
}
|
||||
|
||||
var firstMedia = resolvedItem.Files.First();
|
||||
var firstMedia = resolvedItem.Files[0];
|
||||
|
||||
var libraryItem = new T
|
||||
{
|
||||
|
|
|
@ -1429,7 +1429,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
return result;
|
||||
}
|
||||
|
||||
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> tuples, ItemFields[] fields, User user = null)
|
||||
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> tuples, IReadOnlyList<ItemFields> fields, User user = null)
|
||||
{
|
||||
var programTuples = new List<Tuple<BaseItemDto, string, string>>();
|
||||
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
|
||||
|
@ -2208,7 +2208,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
/// <returns>Task.</returns>
|
||||
public Task ResetTuner(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var parts = id.Split(new[] { '_' }, 2);
|
||||
var parts = id.Split('_', 2);
|
||||
|
||||
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), parts[0], StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
if (string.IsNullOrEmpty(currentFile))
|
||||
{
|
||||
return (files.Last(), true);
|
||||
return (files[^1], true);
|
||||
}
|
||||
|
||||
var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1;
|
||||
|
|
|
@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
private string GetChannelNumber(string extInf, Dictionary<string, string> attributes, string mediaUrl)
|
||||
{
|
||||
var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var nameParts = extInf.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
var nameInExtInf = nameParts.Length > 1 ? nameParts[^1].AsSpan().Trim() : ReadOnlySpan<char>.Empty;
|
||||
|
||||
string numberString = null;
|
||||
|
@ -273,8 +273,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
private static string GetChannelName(string extInf, Dictionary<string, string> attributes)
|
||||
{
|
||||
var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null;
|
||||
var nameParts = extInf.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
var nameInExtInf = nameParts.Length > 1 ? nameParts[^1].Trim() : null;
|
||||
|
||||
// Check for channel number with the format from SatIp
|
||||
// #EXTINF:0,84. VOX Schweiz
|
||||
|
|
|
@ -113,5 +113,7 @@
|
|||
"TaskDownloadMissingSubtitles": "Hiányzó feliratok letöltése",
|
||||
"TaskRefreshChannelsDescription": "Frissíti az internetes csatornák adatait.",
|
||||
"TaskRefreshChannels": "Csatornák frissítése",
|
||||
"TaskCleanTranscodeDescription": "Törli az egy napnál régebbi átkódolási fájlokat."
|
||||
"TaskCleanTranscodeDescription": "Törli az egy napnál régebbi átkódolási fájlokat.",
|
||||
"TaskCleanActivityLogDescription": "A beállítottnál korábbi bejegyzések törlése a tevékenységnaplóból.",
|
||||
"TaskCleanActivityLog": "Tevékenységnapló törlése"
|
||||
}
|
||||
|
|
|
@ -112,5 +112,7 @@
|
|||
"TasksChannelsCategory": "Canale de pe Internet",
|
||||
"TasksApplicationCategory": "Aplicație",
|
||||
"TasksLibraryCategory": "Librărie",
|
||||
"TasksMaintenanceCategory": "Mentenanță"
|
||||
"TasksMaintenanceCategory": "Mentenanță",
|
||||
"TaskCleanActivityLogDescription": "Șterge intrările din jurnalul de activitate mai vechi de data configurată.",
|
||||
"TaskCleanActivityLog": "Curăță Jurnalul de Activitate"
|
||||
}
|
||||
|
|
|
@ -112,5 +112,7 @@
|
|||
"TasksChannelsCategory": "Интернет канали",
|
||||
"TasksApplicationCategory": "Апликација",
|
||||
"TasksLibraryCategory": "Библиотека",
|
||||
"TasksMaintenanceCategory": "Одржавање"
|
||||
"TasksMaintenanceCategory": "Одржавање",
|
||||
"TaskCleanActivityLogDescription": "Брише историју активности старију од конфигурисаног броја година.",
|
||||
"TaskCleanActivityLog": "Очисти историју активности"
|
||||
}
|
||||
|
|
|
@ -112,5 +112,7 @@
|
|||
"UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
|
||||
"HomeVideos": "முகப்பு வீடியோக்கள்",
|
||||
"UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது",
|
||||
"UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
|
||||
"UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது",
|
||||
"TaskCleanActivityLogDescription": "உள்ளமைக்கப்பட்ட வயதை விட பழைய செயல்பாட்டு பதிவு உள்ளீடுகளை நீக்குகிறது.",
|
||||
"TaskCleanActivityLog": "செயல்பாட்டு பதிவை அழி"
|
||||
}
|
||||
|
|
|
@ -653,7 +653,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
try
|
||||
{
|
||||
_logger.LogInformation(Name + ": Waiting on Task");
|
||||
var exited = Task.WaitAll(new[] { task }, 2000);
|
||||
var exited = task.Wait(2000);
|
||||
|
||||
if (exited)
|
||||
{
|
||||
|
|
|
@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
try
|
||||
{
|
||||
previouslyFailedImages = File.ReadAllText(failHistoryPath)
|
||||
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Split('|', StringSplitOptions.RemoveEmptyEntries)
|
||||
.ToList();
|
||||
}
|
||||
catch (IOException)
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
@ -55,9 +56,9 @@ namespace Emby.Server.Implementations.Session
|
|||
connection.Closed += OnConnectionClosed;
|
||||
}
|
||||
|
||||
private void OnConnectionClosed(object sender, EventArgs e)
|
||||
private void OnConnectionClosed(object? sender, EventArgs e)
|
||||
{
|
||||
var connection = (IWebSocketConnection)sender;
|
||||
var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender));
|
||||
_logger.LogDebug("Removing websocket from session {Session}", _session.Id);
|
||||
_sockets.Remove(connection);
|
||||
connection.Closed -= OnConnectionClosed;
|
||||
|
|
|
@ -14,6 +14,7 @@ using Jellyfin.Api.Helpers;
|
|||
using Jellyfin.Api.Models.PlaybackDtos;
|
||||
using Jellyfin.Api.Models.StreamingDtos;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
|
@ -1347,7 +1348,9 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
|
||||
|
||||
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer);
|
||||
var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
|
||||
|
||||
var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer);
|
||||
|
||||
var segmentFormat = GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.');
|
||||
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -1565,8 +1568,7 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
private string GetSegmentPath(StreamState state, string playlist, int index)
|
||||
{
|
||||
var folder = Path.GetDirectoryName(playlist);
|
||||
|
||||
var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException($"Provided path ({playlist}) is not valid.", nameof(playlist));
|
||||
var filename = Path.GetFileNameWithoutExtension(playlist);
|
||||
|
||||
return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request.SegmentContainer));
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Models.EnvironmentDtos;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -103,6 +104,11 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
if (validatePathDto.ValidateWritable)
|
||||
{
|
||||
if (validatePathDto.Path == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(nameof(validatePathDto.Path));
|
||||
}
|
||||
|
||||
var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString());
|
||||
try
|
||||
{
|
||||
|
|
|
@ -78,8 +78,8 @@ namespace Jellyfin.Api.Controllers
|
|||
var query = new InternalItemsQuery
|
||||
{
|
||||
User = user,
|
||||
MediaTypes = (mediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
|
||||
IncludeItemTypes = (includeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
|
||||
MediaTypes = (mediaTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
|
||||
IncludeItemTypes = (includeItemTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
|
||||
Recursive = true,
|
||||
EnableTotalRecordCount = false,
|
||||
DtoOptions = new DtoOptions
|
||||
|
@ -168,7 +168,7 @@ namespace Jellyfin.Api.Controllers
|
|||
var genreQuery = new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes =
|
||||
(includeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
|
||||
(includeItemTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
|
||||
DtoOptions = new DtoOptions
|
||||
{
|
||||
Fields = Array.Empty<ItemFields>(),
|
||||
|
|
|
@ -176,7 +176,7 @@ namespace Jellyfin.Api.Controllers
|
|||
return _dtoService.GetBaseItemDto(item, dtoOptions);
|
||||
}
|
||||
|
||||
private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
|
||||
private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
|
||||
where T : BaseItem, new()
|
||||
{
|
||||
var result = libraryManager.GetItemList(new InternalItemsQuery
|
||||
|
|
|
@ -8,6 +8,7 @@ using Jellyfin.Api.Attributes;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
@ -134,7 +135,8 @@ namespace Jellyfin.Api.Controllers
|
|||
var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath)
|
||||
.FirstOrDefault(i =>
|
||||
string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase)
|
||||
&& i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
|
||||
&& i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
?? throw new ResourceNotFoundException($"Provided path ({transcodeFolderPath}) is not valid.");
|
||||
|
||||
return GetFileResult(file, playlistPath);
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <param name="theme">Theme to search.</param>
|
||||
/// <param name="name">File name to search for.</param>
|
||||
/// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns>
|
||||
private ActionResult GetImageFile(string basePath, string? theme, string? name)
|
||||
private ActionResult GetImageFile(string basePath, string theme, string? name)
|
||||
{
|
||||
var themeFolder = Path.Combine(basePath, theme);
|
||||
if (Directory.Exists(themeFolder))
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
|
@ -1268,7 +1269,7 @@ namespace Jellyfin.Api.Controllers
|
|||
Response.Headers.Add(key, value);
|
||||
}
|
||||
|
||||
Response.ContentType = imageContentType;
|
||||
Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain;
|
||||
Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
|
||||
Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept);
|
||||
|
||||
|
|
|
@ -334,10 +334,16 @@ namespace Jellyfin.Api.Controllers
|
|||
private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
|
||||
{
|
||||
using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
|
||||
if (result.Content.Headers.ContentType?.MediaType == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType));
|
||||
}
|
||||
|
||||
var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
|
||||
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||
var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
|
||||
Directory.CreateDirectory(directory);
|
||||
using (var stream = result.Content)
|
||||
{
|
||||
await using var fileStream = new FileStream(
|
||||
|
@ -351,7 +357,9 @@ namespace Jellyfin.Api.Controllers
|
|||
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||
var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
|
||||
|
||||
Directory.CreateDirectory(pointerCacheDirectory);
|
||||
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -456,7 +456,7 @@ namespace Jellyfin.Api.Controllers
|
|||
: null;
|
||||
|
||||
var dtoOptions = new DtoOptions().AddClientFields(Request);
|
||||
BaseItem parent = item.GetParent();
|
||||
BaseItem? parent = item.GetParent();
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
|
@ -467,7 +467,7 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
|
||||
|
||||
parent = parent.GetParent();
|
||||
parent = parent?.GetParent();
|
||||
}
|
||||
|
||||
return baseItemDtos;
|
||||
|
@ -681,12 +681,12 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
|
||||
/// <response code="200">Similar items returned.</response>
|
||||
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
|
||||
[HttpGet("Artists/{itemId}/Similar")]
|
||||
[HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists")]
|
||||
[HttpGet("Items/{itemId}/Similar")]
|
||||
[HttpGet("Albums/{itemId}/Similar")]
|
||||
[HttpGet("Shows/{itemId}/Similar")]
|
||||
[HttpGet("Movies/{itemId}/Similar")]
|
||||
[HttpGet("Trailers/{itemId}/Similar")]
|
||||
[HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums")]
|
||||
[HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows")]
|
||||
[HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies")]
|
||||
[HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
|
||||
|
@ -893,7 +893,7 @@ namespace Jellyfin.Api.Controllers
|
|||
return _libraryManager.GetItemsResult(query).TotalRecordCount;
|
||||
}
|
||||
|
||||
private BaseItem TranslateParentItem(BaseItem item, User user)
|
||||
private BaseItem? TranslateParentItem(BaseItem item, User user)
|
||||
{
|
||||
return item.GetParent() is AggregateFolder
|
||||
? _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
|
|
|
@ -1073,7 +1073,7 @@ namespace Jellyfin.Api.Controllers
|
|||
var client = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
// https://json.schedulesdirect.org/20141201/available/countries
|
||||
// Can't dispose the response as it's required up the call chain.
|
||||
var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
|
||||
var response = await client.GetAsync(new Uri("https://json.schedulesdirect.org/20141201/available/countries"))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json);
|
||||
|
|
|
@ -139,7 +139,7 @@ namespace Jellyfin.Api.Controllers
|
|||
{
|
||||
var dtoOptions = new DtoOptions().AddClientFields(Request);
|
||||
|
||||
MusicGenre item;
|
||||
MusicGenre? item;
|
||||
|
||||
if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
|
@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers
|
|||
return _dtoService.GetBaseItemDto(item, dtoOptions);
|
||||
}
|
||||
|
||||
private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
|
||||
private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
|
||||
where T : BaseItem, new()
|
||||
{
|
||||
var result = libraryManager.GetItemList(new InternalItemsQuery
|
||||
|
|
|
@ -54,6 +54,11 @@ namespace Jellyfin.Api.Controllers
|
|||
string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -149,12 +154,13 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <param name="repositoryInfos">The list of package repositories.</param>
|
||||
/// <response code="204">Package repositories saved.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpOptions("Repositories")]
|
||||
[HttpPost("Repositories")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos)
|
||||
{
|
||||
_serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos;
|
||||
_serverConfigurationManager.SaveConfiguration();
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,9 +157,9 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesImageFile]
|
||||
public async Task<ActionResult> GetRemoteImage([FromQuery, Required] string imageUrl)
|
||||
public async Task<ActionResult> GetRemoteImage([FromQuery, Required] Uri imageUrl)
|
||||
{
|
||||
var urlHash = imageUrl.GetMD5();
|
||||
var urlHash = imageUrl.ToString().GetMD5();
|
||||
var pointerCachePath = GetFullCachePath(urlHash.ToString());
|
||||
|
||||
string? contentPath = null;
|
||||
|
@ -245,17 +245,25 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <param name="urlHash">The URL hash.</param>
|
||||
/// <param name="pointerCachePath">The pointer cache path.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
|
||||
private async Task DownloadImage(Uri url, Guid urlHash, string pointerCachePath)
|
||||
{
|
||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
|
||||
var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
|
||||
if (response.Content.Headers.ContentType?.MediaType == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(nameof(response.Content.Headers.ContentType));
|
||||
}
|
||||
|
||||
var ext = response.Content.Headers.ContentType.MediaType.Split('/')[^1];
|
||||
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||
var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
|
||||
Directory.CreateDirectory(fullCacheDirectory);
|
||||
await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||
|
||||
var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
|
||||
Directory.CreateDirectory(pointerCacheDirectory);
|
||||
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
private T GetParentWithImage<T>(BaseItem item, ImageType type)
|
||||
private T? GetParentWithImage<T>(BaseItem item, ImageType type)
|
||||
where T : BaseItem
|
||||
{
|
||||
return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type));
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[Produces(MediaTypeNames.Application.Octet)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<FileStreamResult>> GetAttachment(
|
||||
public async Task<ActionResult> GetAttachment(
|
||||
[FromRoute, Required] Guid videoId,
|
||||
[FromRoute, Required] string mediaSourceId,
|
||||
[FromRoute, Required] int index)
|
||||
|
|
|
@ -11,6 +11,7 @@ using Jellyfin.Api.Helpers;
|
|||
using Jellyfin.Api.Models.PlaybackDtos;
|
||||
using Jellyfin.Api.Models.StreamingDtos;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
|
@ -361,7 +362,8 @@ namespace Jellyfin.Api.Controllers
|
|||
var threads = _encodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec);
|
||||
var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions);
|
||||
var format = !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) ? "." + state.Request.SegmentContainer : ".ts";
|
||||
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format;
|
||||
var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
|
||||
var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format;
|
||||
|
||||
var segmentFormat = format.TrimStart('.');
|
||||
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
@ -43,7 +45,7 @@ namespace Jellyfin.Api.Extensions
|
|||
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
int oldLen = dtoOptions.Fields.Length;
|
||||
int oldLen = dtoOptions.Fields.Count;
|
||||
var arr = new ItemFields[oldLen + 1];
|
||||
dtoOptions.Fields.CopyTo(arr, 0);
|
||||
arr[oldLen] = ItemFields.RecursiveItemCount;
|
||||
|
@ -61,7 +63,7 @@ namespace Jellyfin.Api.Extensions
|
|||
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
int oldLen = dtoOptions.Fields.Length;
|
||||
int oldLen = dtoOptions.Fields.Count;
|
||||
var arr = new ItemFields[oldLen + 1];
|
||||
dtoOptions.Fields.CopyTo(arr, 0);
|
||||
arr[oldLen] = ItemFields.ChildCount;
|
||||
|
@ -90,7 +92,7 @@ namespace Jellyfin.Api.Extensions
|
|||
bool? enableImages,
|
||||
bool? enableUserData,
|
||||
int? imageTypeLimit,
|
||||
ImageType[] enableImageTypes)
|
||||
IReadOnlyList<ImageType> enableImageTypes)
|
||||
{
|
||||
dtoOptions.EnableImages = enableImages ?? true;
|
||||
|
||||
|
@ -104,7 +106,7 @@ namespace Jellyfin.Api.Extensions
|
|||
dtoOptions.EnableUserData = enableUserData.Value;
|
||||
}
|
||||
|
||||
if (enableImageTypes.Length != 0)
|
||||
if (enableImageTypes.Count != 0)
|
||||
{
|
||||
dtoOptions.ImageTypes = enableImageTypes;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using System.Net.Http;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Models.StreamingDtos;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
|
@ -98,6 +100,11 @@ namespace Jellyfin.Api.Helpers
|
|||
TranscodingJobType transcodingJobType,
|
||||
StreamingRequestDto streamingRequest)
|
||||
{
|
||||
if (_httpContextAccessor.HttpContext == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
|
||||
}
|
||||
|
||||
bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head;
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ namespace Jellyfin.Api.Helpers
|
|||
StreamingRequestDto streamingRequest,
|
||||
bool enableAdaptiveBitrateStreaming)
|
||||
{
|
||||
var isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == WebRequestMethods.Http.Head;
|
||||
var isHeadRequest = _httpContextAccessor.HttpContext?.Request.Method == WebRequestMethods.Http.Head;
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
return await GetMasterPlaylistInternal(
|
||||
streamingRequest,
|
||||
|
@ -130,6 +130,11 @@ namespace Jellyfin.Api.Helpers
|
|||
TranscodingJobType transcodingJobType,
|
||||
CancellationTokenSource cancellationTokenSource)
|
||||
{
|
||||
if (_httpContextAccessor.HttpContext == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
|
||||
}
|
||||
|
||||
using var state = await StreamingHelpers.GetStreamingState(
|
||||
streamingRequest,
|
||||
_httpContextAccessor.HttpContext.Request,
|
||||
|
@ -487,14 +492,14 @@ namespace Jellyfin.Api.Helpers
|
|||
|
||||
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
|
||||
string? profile = state.GetRequestedProfiles("h264").FirstOrDefault();
|
||||
return HlsCodecStringHelpers.GetH264String(profile, level);
|
||||
}
|
||||
|
||||
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
|
||||
string? profile = state.GetRequestedProfiles("h265").FirstOrDefault();
|
||||
|
||||
return HlsCodecStringHelpers.GetH265String(profile, level);
|
||||
}
|
||||
|
|
|
@ -37,8 +37,8 @@ namespace Jellyfin.Api.Helpers
|
|||
}
|
||||
|
||||
// Can't dispose the response as it's required up the call chain.
|
||||
var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false);
|
||||
var contentType = response.Content.Headers.ContentType.ToString();
|
||||
var response = await httpClient.GetAsync(new Uri(state.MediaPath)).ConfigureAwait(false);
|
||||
var contentType = response.Content.Headers.ContentType?.ToString();
|
||||
|
||||
httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Jellyfin.Api.Helpers
|
|||
/// </summary>
|
||||
/// <param name="profile">AAC profile.</param>
|
||||
/// <returns>AAC codec string.</returns>
|
||||
public static string GetAACString(string profile)
|
||||
public static string GetAACString(string? profile)
|
||||
{
|
||||
StringBuilder result = new StringBuilder("mp4a", 9);
|
||||
|
||||
|
@ -46,7 +46,7 @@ namespace Jellyfin.Api.Helpers
|
|||
/// <param name="profile">H.264 profile.</param>
|
||||
/// <param name="level">H.264 level.</param>
|
||||
/// <returns>H.264 string.</returns>
|
||||
public static string GetH264String(string profile, int level)
|
||||
public static string GetH264String(string? profile, int level)
|
||||
{
|
||||
StringBuilder result = new StringBuilder("avc1", 11);
|
||||
|
||||
|
@ -80,7 +80,7 @@ namespace Jellyfin.Api.Helpers
|
|||
/// <param name="profile">H.265 profile.</param>
|
||||
/// <param name="level">H.265 level.</param>
|
||||
/// <returns>H.265 string.</returns>
|
||||
public static string GetH265String(string profile, int level)
|
||||
public static string GetH265String(string? profile, int level)
|
||||
{
|
||||
// The h265 syntax is a bit of a mystery at the time this comment was written.
|
||||
// This is what I've found through various sources:
|
||||
|
|
|
@ -45,6 +45,11 @@ namespace Jellyfin.Api.Helpers
|
|||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||
if (line == null)
|
||||
{
|
||||
// Nothing currently in buffer.
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Models.PlaybackDtos;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
|
@ -90,6 +91,11 @@ namespace Jellyfin.Api.Helpers
|
|||
allowAsyncFileRead = true;
|
||||
}
|
||||
|
||||
if (_path == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(nameof(_path));
|
||||
}
|
||||
|
||||
await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
|
||||
|
||||
var eofCount = 0;
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace Jellyfin.Api.Helpers
|
|||
}
|
||||
|
||||
return removeEmpty
|
||||
? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
|
||||
? value.Split(separator, StringSplitOptions.RemoveEmptyEntries)
|
||||
: value.Split(separator);
|
||||
}
|
||||
|
||||
|
|
|
@ -83,8 +83,12 @@ namespace Jellyfin.Api.Helpers
|
|||
}
|
||||
|
||||
streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
|
||||
if (httpRequest.Path.Value == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(nameof(httpRequest.Path));
|
||||
}
|
||||
|
||||
var url = httpRequest.Path.Value.Split('.').Last();
|
||||
var url = httpRequest.Path.Value.Split('.')[^1];
|
||||
|
||||
if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@ using Jellyfin.Api.Models.PlaybackDtos;
|
|||
using Jellyfin.Api.Models.StreamingDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
|
@ -102,7 +103,7 @@ namespace Jellyfin.Api.Helpers
|
|||
/// </summary>
|
||||
/// <param name="playSessionId">Playback session id.</param>
|
||||
/// <returns>The transcoding job.</returns>
|
||||
public TranscodingJobDto GetTranscodingJob(string playSessionId)
|
||||
public TranscodingJobDto? GetTranscodingJob(string playSessionId)
|
||||
{
|
||||
lock (_activeTranscodingJobs)
|
||||
{
|
||||
|
@ -116,7 +117,7 @@ namespace Jellyfin.Api.Helpers
|
|||
/// <param name="path">Path to the transcoding file.</param>
|
||||
/// <param name="type">The <see cref="TranscodingJobType"/>.</param>
|
||||
/// <returns>The transcoding job.</returns>
|
||||
public TranscodingJobDto GetTranscodingJob(string path, TranscodingJobType type)
|
||||
public TranscodingJobDto? GetTranscodingJob(string path, TranscodingJobType type)
|
||||
{
|
||||
lock (_activeTranscodingJobs)
|
||||
{
|
||||
|
@ -193,10 +194,9 @@ namespace Jellyfin.Api.Helpers
|
|||
/// Called when [transcode kill timer stopped].
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
private async void OnTranscodeKillTimerStopped(object state)
|
||||
private async void OnTranscodeKillTimerStopped(object? state)
|
||||
{
|
||||
var job = (TranscodingJobDto)state;
|
||||
|
||||
var job = state as TranscodingJobDto ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJobDto)}", nameof(state));
|
||||
if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
|
||||
{
|
||||
var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
|
||||
|
@ -489,7 +489,8 @@ namespace Jellyfin.Api.Helpers
|
|||
CancellationTokenSource cancellationTokenSource,
|
||||
string? workingDirectory = null)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||
var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
||||
|
||||
|
@ -523,7 +524,7 @@ namespace Jellyfin.Api.Helpers
|
|||
RedirectStandardInput = true,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = commandLineArguments,
|
||||
WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory,
|
||||
WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? string.Empty : workingDirectory,
|
||||
ErrorDialog = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
|
@ -827,7 +828,7 @@ namespace Jellyfin.Api.Helpers
|
|||
{
|
||||
lock (_transcodingLocks)
|
||||
{
|
||||
if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim result))
|
||||
if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim? result))
|
||||
{
|
||||
result = new SemaphoreSlim(1, 1);
|
||||
_transcodingLocks[outputPath] = result;
|
||||
|
@ -837,7 +838,7 @@ namespace Jellyfin.Api.Helpers
|
|||
}
|
||||
}
|
||||
|
||||
private void OnPlaybackProgress(object sender, PlaybackProgressEventArgs e)
|
||||
private void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
|
||||
{
|
||||
|
|
|
@ -6,17 +6,19 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
|
||||
<NoWarn>AD0001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.9" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.ModelBinders
|
||||
{
|
||||
|
@ -11,6 +13,17 @@ namespace Jellyfin.Api.ModelBinders
|
|||
/// </summary>
|
||||
public class CommaDelimitedArrayModelBinder : IModelBinder
|
||||
{
|
||||
private readonly ILogger<CommaDelimitedArrayModelBinder> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommaDelimitedArrayModelBinder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger{CommaDelimitedArrayModelBinder}"/> interface.</param>
|
||||
public CommaDelimitedArrayModelBinder(ILogger<CommaDelimitedArrayModelBinder> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
|
@ -20,16 +33,8 @@ namespace Jellyfin.Api.ModelBinders
|
|||
|
||||
if (valueProviderResult.Length > 1)
|
||||
{
|
||||
var result = Array.CreateInstance(elementType, valueProviderResult.Length);
|
||||
|
||||
for (int i = 0; i < valueProviderResult.Length; i++)
|
||||
{
|
||||
var value = converter.ConvertFromString(valueProviderResult.Values[i].Trim());
|
||||
|
||||
result.SetValue(value, i);
|
||||
}
|
||||
|
||||
bindingContext.Result = ModelBindingResult.Success(result);
|
||||
var typedValues = GetParsedResult(valueProviderResult.Values, elementType, converter);
|
||||
bindingContext.Result = ModelBindingResult.Success(typedValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -37,13 +42,8 @@ namespace Jellyfin.Api.ModelBinders
|
|||
|
||||
if (value != null)
|
||||
{
|
||||
var values = Array.ConvertAll(
|
||||
value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries),
|
||||
x => converter.ConvertFromString(x?.Trim()));
|
||||
|
||||
var typedValues = Array.CreateInstance(elementType, values.Length);
|
||||
values.CopyTo(typedValues, 0);
|
||||
|
||||
var splitValues = value.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
var typedValues = GetParsedResult(splitValues, elementType, converter);
|
||||
bindingContext.Result = ModelBindingResult.Success(typedValues);
|
||||
}
|
||||
else
|
||||
|
@ -55,5 +55,36 @@ namespace Jellyfin.Api.ModelBinders
|
|||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Array GetParsedResult(IReadOnlyList<string> values, Type elementType, TypeConverter converter)
|
||||
{
|
||||
var parsedValues = new object?[values.Count];
|
||||
var convertedCount = 0;
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
parsedValues[i] = converter.ConvertFromString(values[i].Trim());
|
||||
convertedCount++;
|
||||
}
|
||||
catch (FormatException e)
|
||||
{
|
||||
_logger.LogWarning(e, "Error converting value.");
|
||||
}
|
||||
}
|
||||
|
||||
var typedValues = Array.CreateInstance(elementType, convertedCount);
|
||||
var typedValueIndex = 0;
|
||||
for (var i = 0; i < parsedValues.Length; i++)
|
||||
{
|
||||
if (parsedValues[i] != null)
|
||||
{
|
||||
typedValues.SetValue(parsedValues[i], typedValueIndex);
|
||||
typedValueIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return typedValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Jellyfin.Api.Models.LibraryDtos
|
||||
{
|
||||
|
@ -10,25 +11,21 @@ namespace Jellyfin.Api.Models.LibraryDtos
|
|||
/// <summary>
|
||||
/// Gets or sets the metadata savers.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataSavers", Justification = "Imported from ServiceStack")]
|
||||
public LibraryOptionInfoDto[] MetadataSavers { get; set; } = null!;
|
||||
public IReadOnlyList<LibraryOptionInfoDto> MetadataSavers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the metadata readers.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataReaders", Justification = "Imported from ServiceStack")]
|
||||
public LibraryOptionInfoDto[] MetadataReaders { get; set; } = null!;
|
||||
public IReadOnlyList<LibraryOptionInfoDto> MetadataReaders { get; set; } = Array.Empty<LibraryOptionInfoDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the subtitle fetchers.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "SubtitleFetchers", Justification = "Imported from ServiceStack")]
|
||||
public LibraryOptionInfoDto[] SubtitleFetchers { get; set; } = null!;
|
||||
public IReadOnlyList<LibraryOptionInfoDto> SubtitleFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type options.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "TypeOptions", Justification = "Imported from ServiceStack")]
|
||||
public LibraryTypeOptionsDto[] TypeOptions { get; set; } = null!;
|
||||
public IReadOnlyList<LibraryTypeOptionsDto> TypeOptions { get; set; } = Array.Empty<LibraryTypeOptionsDto>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
|
@ -17,25 +18,21 @@ namespace Jellyfin.Api.Models.LibraryDtos
|
|||
/// <summary>
|
||||
/// Gets or sets the metadata fetchers.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataFetchers", Justification = "Imported from ServiceStack")]
|
||||
public LibraryOptionInfoDto[] MetadataFetchers { get; set; } = null!;
|
||||
public IReadOnlyList<LibraryOptionInfoDto> MetadataFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image fetchers.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "ImageFetchers", Justification = "Imported from ServiceStack")]
|
||||
public LibraryOptionInfoDto[] ImageFetchers { get; set; } = null!;
|
||||
public IReadOnlyList<LibraryOptionInfoDto> ImageFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the supported image types.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "SupportedImageTypes", Justification = "Imported from ServiceStack")]
|
||||
public ImageType[] SupportedImageTypes { get; set; } = null!;
|
||||
public IReadOnlyList<ImageType> SupportedImageTypes { get; set; } = Array.Empty<ImageType>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default image options.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "DefaultImageOptions", Justification = "Imported from ServiceStack")]
|
||||
public ImageOption[] DefaultImageOptions { get; set; } = null!;
|
||||
public IReadOnlyList<ImageOption> DefaultImageOptions { get; set; } = Array.Empty<ImageOption>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
@ -25,8 +26,7 @@ namespace Jellyfin.Api.Models.LiveTvDtos
|
|||
/// <summary>
|
||||
/// Gets or sets list of mappings.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:DontReturnArrays", MessageId = "Mappings", Justification = "Imported from ServiceStack")]
|
||||
public NameValuePair[] Mappings { get; set; } = null!;
|
||||
public IReadOnlyList<NameValuePair> Mappings { get; set; } = Array.Empty<NameValuePair>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets provider name.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using MediaBrowser.Common.Json.Converters;
|
||||
|
@ -143,8 +144,7 @@ namespace Jellyfin.Api.Models.LiveTvDtos
|
|||
/// Optional.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "EnableImageTypes", Justification = "Imported from ServiceStack")]
|
||||
public ImageType[] EnableImageTypes { get; set; } = Array.Empty<ImageType>();
|
||||
public IReadOnlyList<ImageType> EnableImageTypes { get; set; } = Array.Empty<ImageType>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets include user data.
|
||||
|
@ -169,7 +169,6 @@ namespace Jellyfin.Api.Models.LiveTvDtos
|
|||
/// Optional.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "Fields", Justification = "Imported from ServiceStack")]
|
||||
public ItemFields[] Fields { get; set; } = Array.Empty<ItemFields>();
|
||||
public IReadOnlyList<ItemFields> Fields { get; set; } = Array.Empty<ItemFields>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
||||
|
@ -17,8 +18,6 @@ namespace Jellyfin.Api.Models.MediaInfoDtos
|
|||
/// <summary>
|
||||
/// Gets or sets the device play protocols.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:DontReturnArrays", MessageId = "DevicePlayProtocols", Justification = "Imported from ServiceStack")]
|
||||
[SuppressMessage("Microsoft.Performance", "SA1011:ClosingBracketsSpace", MessageId = "DevicePlayProtocols", Justification = "Imported from ServiceStack")]
|
||||
public MediaProtocol[]? DirectPlayProtocols { get; set; }
|
||||
public IReadOnlyList<MediaProtocol> DirectPlayProtocols { get; set; } = Array.Empty<MediaProtocol>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,7 +196,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||
/// Start kill timer.
|
||||
/// </summary>
|
||||
/// <param name="callback">Callback action.</param>
|
||||
public void StartKillTimer(Action<object> callback)
|
||||
public void StartKillTimer(Action<object?> callback)
|
||||
{
|
||||
StartKillTimer(callback, PingTimeout);
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||
/// </summary>
|
||||
/// <param name="callback">Callback action.</param>
|
||||
/// <param name="intervalMs">Callback interval.</param>
|
||||
public void StartKillTimer(Action<object> callback, int intervalMs)
|
||||
public void StartKillTimer(Action<object?> callback, int intervalMs)
|
||||
{
|
||||
if (HasExited)
|
||||
{
|
||||
|
|
|
@ -101,7 +101,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||
return _config.GetConfiguration<EncodingOptions>("encoding");
|
||||
}
|
||||
|
||||
private async void TimerCallback(object state)
|
||||
private async void TimerCallback(object? state)
|
||||
{
|
||||
if (_job.HasExited)
|
||||
{
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace Jellyfin.Api.WebSocketListeners
|
|||
base.Dispose(dispose);
|
||||
}
|
||||
|
||||
private void OnEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
private void OnEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
{
|
||||
SendData(true);
|
||||
}
|
||||
|
|
|
@ -64,19 +64,19 @@ namespace Jellyfin.Api.WebSocketListeners
|
|||
base.Dispose(dispose);
|
||||
}
|
||||
|
||||
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
private void OnTaskCompleted(object? sender, TaskCompletionEventArgs e)
|
||||
{
|
||||
SendData(true);
|
||||
e.Task.TaskProgress -= OnTaskProgress;
|
||||
}
|
||||
|
||||
private void OnTaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e)
|
||||
private void OnTaskExecuting(object? sender, GenericEventArgs<IScheduledTaskWorker> e)
|
||||
{
|
||||
SendData(true);
|
||||
e.Argument.TaskProgress += OnTaskProgress;
|
||||
}
|
||||
|
||||
private void OnTaskProgress(object sender, GenericEventArgs<double> e)
|
||||
private void OnTaskProgress(object? sender, GenericEventArgs<double> e)
|
||||
{
|
||||
SendData(false);
|
||||
}
|
||||
|
|
|
@ -66,37 +66,37 @@ namespace Jellyfin.Api.WebSocketListeners
|
|||
base.Dispose(dispose);
|
||||
}
|
||||
|
||||
private async void OnSessionManagerSessionActivity(object sender, SessionEventArgs e)
|
||||
private async void OnSessionManagerSessionActivity(object? sender, SessionEventArgs e)
|
||||
{
|
||||
await SendData(false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e)
|
||||
private async void OnSessionManagerCapabilitiesChanged(object? sender, SessionEventArgs e)
|
||||
{
|
||||
await SendData(true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e)
|
||||
private async void OnSessionManagerPlaybackProgress(object? sender, PlaybackProgressEventArgs e)
|
||||
{
|
||||
await SendData(!e.IsAutomated).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||
private async void OnSessionManagerPlaybackStopped(object? sender, PlaybackStopEventArgs e)
|
||||
{
|
||||
await SendData(true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
private async void OnSessionManagerPlaybackStart(object? sender, PlaybackProgressEventArgs e)
|
||||
{
|
||||
await SendData(true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnSessionManagerSessionEnded(object sender, SessionEventArgs e)
|
||||
private async void OnSessionManagerSessionEnded(object? sender, SessionEventArgs e)
|
||||
{
|
||||
await SendData(true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnSessionManagerSessionStarted(object sender, SessionEventArgs e)
|
||||
private async void OnSessionManagerSessionStarted(object? sender, SessionEventArgs e)
|
||||
{
|
||||
await SendData(true).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
@ -41,8 +41,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
|||
using System.IO;
|
||||
using BlurHashSharp.SkiaSharp;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
|
@ -227,8 +228,8 @@ namespace Jellyfin.Drawing.Skia
|
|||
}
|
||||
|
||||
var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path));
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
|
||||
var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException($"Provided path ({tempPath}) is not valid.");
|
||||
Directory.CreateDirectory(directory);
|
||||
File.Copy(path, tempPath, true);
|
||||
|
||||
return tempPath;
|
||||
|
@ -493,7 +494,8 @@ namespace Jellyfin.Drawing.Skia
|
|||
// If all we're doing is resizing then we can stop now
|
||||
if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||
var outputDirectory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
using var outputStream = new SKFileWStream(outputPath);
|
||||
using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels());
|
||||
resizedBitmap.Encode(outputStream, skiaOutputFormat, quality);
|
||||
|
@ -540,7 +542,8 @@ namespace Jellyfin.Drawing.Skia
|
|||
DrawIndicator(canvas, width, height, options);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||
var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
|
||||
Directory.CreateDirectory(directory);
|
||||
using (var outputStream = new SKFileWStream(outputPath))
|
||||
{
|
||||
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels()))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
@ -24,12 +24,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Linq.Async" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.9">
|
||||
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.9">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -57,7 +57,8 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
SerializablePasswordReset spr;
|
||||
await using (var str = File.OpenRead(resetFile))
|
||||
{
|
||||
spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
|
||||
spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false)
|
||||
?? throw new ResourceNotFoundException($"Provided path ({resetFile}) is not valid.");
|
||||
}
|
||||
|
||||
if (spr.ExpirationDate < DateTime.UtcNow)
|
||||
|
|
|
@ -26,22 +26,22 @@ namespace Jellyfin.Server.Filters
|
|||
if (attribute is ProducesFileAttribute producesFileAttribute)
|
||||
{
|
||||
// Get operation response values.
|
||||
var (_, value) = operation.Responses
|
||||
var response = operation.Responses
|
||||
.FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal));
|
||||
|
||||
// Operation doesn't have a response.
|
||||
if (value == null)
|
||||
if (response.Value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clear existing responses.
|
||||
value.Content.Clear();
|
||||
response.Value.Content.Clear();
|
||||
|
||||
// Add all content-types as file.
|
||||
foreach (var contentType in producesFileAttribute.GetContentTypes())
|
||||
{
|
||||
value.Content.Add(contentType, _openApiMediaType);
|
||||
response.Value.Content.Add(contentType, _openApiMediaType);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
@ -30,7 +30,8 @@ namespace Jellyfin.Server.Formatters
|
|||
/// <returns>Write stream task.</returns>
|
||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||
{
|
||||
return context.HttpContext.Response.WriteAsync(context.Object?.ToString());
|
||||
var stringResponse = context.Object?.ToString();
|
||||
return stringResponse == null ? Task.CompletedTask : context.HttpContext.Response.WriteAsync(stringResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ namespace Jellyfin.Server.Formatters
|
|||
/// <inheritdoc />
|
||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||
{
|
||||
return context.HttpContext.Response.WriteAsync(context.Object?.ToString());
|
||||
var stringResponse = context.Object?.ToString();
|
||||
return stringResponse == null ? Task.CompletedTask : context.HttpContext.Response.WriteAsync(stringResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<PropertyGroup>
|
||||
<AssemblyName>jellyfin</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
@ -38,10 +38,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.0" />
|
||||
<PackageReference Include="prometheus-net" Version="4.0.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
|
|
|
@ -9,6 +9,7 @@ using Jellyfin.Data.Entities;
|
|||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
@ -26,6 +27,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
private readonly IServerApplicationPaths _paths;
|
||||
private readonly JellyfinDbProvider _provider;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrateDisplayPreferencesDb"/> class.
|
||||
|
@ -33,11 +35,17 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="paths">The server application paths.</param>
|
||||
/// <param name="provider">The database provider.</param>
|
||||
public MigrateDisplayPreferencesDb(ILogger<MigrateDisplayPreferencesDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider)
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
public MigrateDisplayPreferencesDb(
|
||||
ILogger<MigrateDisplayPreferencesDb> logger,
|
||||
IServerApplicationPaths paths,
|
||||
JellyfinDbProvider provider,
|
||||
IUserManager userManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
_provider = provider;
|
||||
_userManager = userManager;
|
||||
_jsonOptions = new JsonSerializerOptions();
|
||||
_jsonOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
}
|
||||
|
@ -86,11 +94,19 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
continue;
|
||||
}
|
||||
|
||||
var dtoUserId = new Guid(result[1].ToBlob());
|
||||
var existingUser = _userManager.GetUserById(dtoUserId);
|
||||
if (existingUser == null)
|
||||
{
|
||||
_logger.LogWarning("User with ID {UserId} does not exist in the database, skipping migration.", dtoUserId);
|
||||
continue;
|
||||
}
|
||||
|
||||
var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version)
|
||||
? chromecastDict[version]
|
||||
: ChromecastVersion.Stable;
|
||||
|
||||
var displayPreferences = new DisplayPreferences(new Guid(result[1].ToBlob()), result[2].ToString())
|
||||
var displayPreferences = new DisplayPreferences(dtoUserId, result[2].ToString())
|
||||
{
|
||||
IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null,
|
||||
ShowBackdrop = dto.ShowBackdrop,
|
||||
|
|
|
@ -46,6 +46,13 @@ namespace MediaBrowser.Common.Configuration
|
|||
/// <param name="newConfiguration">The new configuration.</param>
|
||||
void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Manually pre-loads a factory so that it is available pre system initialisation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Class to register.</typeparam>
|
||||
void RegisterConfiguration<T>()
|
||||
where T : IConfigurationFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configuration.
|
||||
/// </summary>
|
||||
|
|
|
@ -26,19 +26,40 @@ namespace MediaBrowser.Common.Json.Converters
|
|||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
var stringEntries = reader.GetString()?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var stringEntries = reader.GetString()?.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (stringEntries == null || stringEntries.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
var entries = new T[stringEntries.Length];
|
||||
var parsedValues = new object[stringEntries.Length];
|
||||
var convertedCount = 0;
|
||||
for (var i = 0; i < stringEntries.Length; i++)
|
||||
{
|
||||
entries[i] = (T)_typeConverter.ConvertFrom(stringEntries[i].Trim());
|
||||
try
|
||||
{
|
||||
parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim());
|
||||
convertedCount++;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// TODO log when upgraded to .Net5
|
||||
// _logger.LogWarning(e, "Error converting value.");
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
var typedValues = new T[convertedCount];
|
||||
var typedValueIndex = 0;
|
||||
for (var i = 0; i < stringEntries.Length; i++)
|
||||
{
|
||||
if (parsedValues[i] != null)
|
||||
{
|
||||
typedValues.SetValue(parsedValues[i], typedValueIndex);
|
||||
typedValueIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return typedValues;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<T[]>(ref reader, options);
|
||||
|
@ -50,4 +71,4 @@ namespace MediaBrowser.Common.Json.Converters
|
|||
JsonSerializer.Serialize(writer, value, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.9" />
|
||||
<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>
|
||||
|
@ -29,7 +29,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -83,16 +83,6 @@ namespace MediaBrowser.Common.Plugins
|
|||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void RegisterServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void UnregisterServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion)
|
||||
{
|
||||
|
@ -185,6 +175,11 @@ namespace MediaBrowser.Common.Plugins
|
|||
/// <value>The type of the configuration.</value>
|
||||
public Type ConfigurationType => typeof(TConfigurationType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the event handler that is triggered when this configuration changes.
|
||||
/// </summary>
|
||||
public EventHandler<BasePluginConfiguration> ConfigurationChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name the assembly file.
|
||||
/// </summary>
|
||||
|
@ -280,6 +275,8 @@ namespace MediaBrowser.Common.Plugins
|
|||
Configuration = (TConfigurationType)configuration;
|
||||
|
||||
SaveConfiguration();
|
||||
|
||||
ConfigurationChanged.Invoke(this, configuration);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -62,18 +62,6 @@ namespace MediaBrowser.Common.Plugins
|
|||
/// Called when just before the plugin is uninstalled from the server.
|
||||
/// </summary>
|
||||
void OnUninstalling();
|
||||
|
||||
/// <summary>
|
||||
/// Registers the plugin's services to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
void RegisterServices(IServiceCollection serviceCollection);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the plugin's services from the service collection.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
void UnregisterServices(IServiceCollection serviceCollection);
|
||||
}
|
||||
|
||||
public interface IHasPluginConfiguration
|
||||
|
|
19
MediaBrowser.Common/Plugins/IPluginServiceRegistrator.cs
Normal file
19
MediaBrowser.Common/Plugins/IPluginServiceRegistrator.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace MediaBrowser.Common.Plugins
|
||||
{
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="IPluginServiceRegistrator" />.
|
||||
/// </summary>
|
||||
public interface IPluginServiceRegistrator
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the plugin's services with the service collection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This interface is only used for service registration and requires a parameterless constructor.
|
||||
/// </remarks>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
void RegisterServices(IServiceCollection serviceCollection);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
@ -15,9 +16,9 @@ namespace MediaBrowser.Controller.Dto
|
|||
ItemFields.RefreshState
|
||||
};
|
||||
|
||||
public ItemFields[] Fields { get; set; }
|
||||
public IReadOnlyList<ItemFields> Fields { get; set; }
|
||||
|
||||
public ImageType[] ImageTypes { get; set; }
|
||||
public IReadOnlyList<ImageType> ImageTypes { get; set; }
|
||||
|
||||
public int ImageTypeLimit { get; set; }
|
||||
|
||||
|
|
|
@ -87,6 +87,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
public const string InterviewFolderName = "interviews";
|
||||
public const string SceneFolderName = "scenes";
|
||||
public const string SampleFolderName = "samples";
|
||||
public const string ShortsFolderName = "shorts";
|
||||
public const string FeaturettesFolderName = "featurettes";
|
||||
|
||||
public static readonly string[] AllExtrasTypesFolderNames = {
|
||||
ExtrasFolderName,
|
||||
|
@ -94,7 +96,9 @@ namespace MediaBrowser.Controller.Entities
|
|||
DeletedScenesFolderName,
|
||||
InterviewFolderName,
|
||||
SceneFolderName,
|
||||
SampleFolderName
|
||||
SampleFolderName,
|
||||
ShortsFolderName,
|
||||
FeaturettesFolderName
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
|
|
|
@ -225,7 +225,7 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, ItemFields[] fields, User user = null);
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
else if (part.StartsWith("fps=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rate = part.Split(new[] { '=' }, 2)[^1];
|
||||
var rate = part.Split('=', 2)[^1];
|
||||
|
||||
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
|
||||
{
|
||||
|
@ -103,7 +103,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
else if (state.RunTimeTicks.HasValue &&
|
||||
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var time = part.Split(new[] { '=' }, 2).Last();
|
||||
var time = part.Split('=', 2)[^1];
|
||||
|
||||
if (TimeSpan.TryParse(time, _usCulture, out var val))
|
||||
{
|
||||
|
@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var size = part.Split(new[] { '=' }, 2).Last();
|
||||
var size = part.Split('=', 2)[^1];
|
||||
|
||||
int? scale = null;
|
||||
if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
|
@ -135,7 +135,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rate = part.Split(new[] { '=' }, 2).Last();
|
||||
var rate = part.Split('=', 2)[^1];
|
||||
|
||||
int? scale = null;
|
||||
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
|
|
|
@ -486,7 +486,7 @@ namespace MediaBrowser.LocalMetadata.Images
|
|||
return false;
|
||||
}
|
||||
|
||||
private FileSystemMetadata GetImage(IEnumerable<FileSystemMetadata> files, string name)
|
||||
private FileSystemMetadata? GetImage(IEnumerable<FileSystemMetadata> files, string name)
|
||||
{
|
||||
return files.FirstOrDefault(i => !i.IsDirectory && string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase) && i.Length > 0);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
|
|
@ -683,7 +683,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
|
|||
default:
|
||||
{
|
||||
string readerName = reader.Name;
|
||||
if (_validProviderIds!.TryGetValue(readerName, out string providerIdValue))
|
||||
if (_validProviderIds!.TryGetValue(readerName, out string? providerIdValue))
|
||||
{
|
||||
var id = reader.ReadElementContentAsString();
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user