Merge branch 'master' into network-rewrite

This commit is contained in:
Shadowghost 2023-03-03 10:42:24 +01:00
commit 80b8661008
102 changed files with 1210 additions and 680 deletions

View File

@ -27,11 +27,11 @@ jobs:
dotnet-version: '7.0.x' dotnet-version: '7.0.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2 uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2 uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2 uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2

View File

@ -103,7 +103,7 @@ jobs:
body="${body//$'\r'/'%0D'}" body="${body//$'\r'/'%0D'}"
echo ::set-output name=body::$body echo ::set-output name=body::$body
- name: Find difference comment - name: Find difference comment
uses: peter-evans/find-comment@85a676a52594b4481e0532825a2d8906ef96dac2 # v2 uses: peter-evans/find-comment@034abe94d3191f9c89d870519735beae326f2bdb # v2
id: find-comment id: find-comment
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}

View File

@ -163,6 +163,7 @@
- [vgambier](https://github.com/vgambier) - [vgambier](https://github.com/vgambier)
- [MinecraftPlaye](https://github.com/MinecraftPlaye) - [MinecraftPlaye](https://github.com/MinecraftPlaye)
- [RealGreenDragon](https://github.com/RealGreenDragon) - [RealGreenDragon](https://github.com/RealGreenDragon)
- [ipitio](https://github.com/ipitio)
# Emby Contributors # Emby Contributors

View File

@ -6,9 +6,9 @@
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.--> <!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
<ItemGroup Label="Package Dependencies"> <ItemGroup Label="Package Dependencies">
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.0" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.17.0" /> <PackageVersion Include="AutoFixture.Xunit2" Version="4.18.0" />
<PackageVersion Include="AutoFixture" Version="4.17.0" /> <PackageVersion Include="AutoFixture" Version="4.18.0" />
<PackageVersion Include="BDInfo" Version="0.7.6.2" /> <PackageVersion Include="BDInfo" Version="0.7.6.2" />
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.2.0" /> <PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
<PackageVersion Include="BlurHashSharp" Version="1.2.0" /> <PackageVersion Include="BlurHashSharp" Version="1.2.0" />
@ -17,7 +17,7 @@
<PackageVersion Include="Diacritics" Version="3.3.14" /> <PackageVersion Include="Diacritics" Version="3.3.14" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" /> <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" /> <PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.8.3" /> <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.8.5" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.5" /> <PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" /> <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="3.6.10" /> <PackageVersion Include="libse" Version="3.6.10" />
@ -46,7 +46,7 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" /> <PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" /> <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="MimeTypes" Version="2.4.0" /> <PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" /> <PackageVersion Include="Mono.Nat" Version="3.0.4" />

View File

@ -27,7 +27,7 @@ namespace Emby.Dlna.ConnectionManager
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns> /// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
private static IEnumerable<StateVariable> GetStateVariables() private static IEnumerable<StateVariable> GetStateVariables()
{ {
var list = new List<StateVariable> return new StateVariable[]
{ {
new StateVariable new StateVariable
{ {
@ -114,8 +114,6 @@ namespace Emby.Dlna.ConnectionManager
SendsEvents = false SendsEvents = false
} }
}; };
return list;
} }
} }
} }

View File

@ -27,7 +27,7 @@ namespace Emby.Dlna.ContentDirectory
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns> /// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
private static IEnumerable<StateVariable> GetStateVariables() private static IEnumerable<StateVariable> GetStateVariables()
{ {
var list = new List<StateVariable> return new StateVariable[]
{ {
new StateVariable new StateVariable
{ {
@ -154,8 +154,6 @@ namespace Emby.Dlna.ContentDirectory
SendsEvents = false SendsEvents = false
} }
}; };
return list;
} }
} }
} }

View File

@ -147,11 +147,16 @@ namespace Emby.Dlna.Server
} }
} }
private string GetFriendlyName() internal string GetFriendlyName()
{ {
if (string.IsNullOrEmpty(_profile.FriendlyName)) if (string.IsNullOrEmpty(_profile.FriendlyName))
{ {
return "Jellyfin - " + _serverName; return _serverName;
}
if (!_profile.FriendlyName.Contains("${HostName}", StringComparison.OrdinalIgnoreCase))
{
return _profile.FriendlyName;
} }
var characterList = new List<char>(); var characterList = new List<char>();
@ -164,13 +169,18 @@ namespace Emby.Dlna.Server
} }
} }
var characters = characterList.ToArray(); var serverName = string.Create(
characterList.Count,
characterList,
(dest, source) =>
{
for (int i = 0; i < dest.Length; i++)
{
dest[i] = source[i];
}
});
var serverName = new string(characters); return _profile.FriendlyName.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
var name = _profile.FriendlyName?.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
return name ?? string.Empty;
} }
private void AppendIconList(StringBuilder builder) private void AppendIconList(StringBuilder builder)

View File

@ -32,7 +32,7 @@ namespace Emby.Naming.AudioBook
var fileName = Path.GetFileNameWithoutExtension(path); var fileName = Path.GetFileNameWithoutExtension(path);
foreach (var expression in _options.AudioBookPartsExpressions) foreach (var expression in _options.AudioBookPartsExpressions)
{ {
var match = new Regex(expression, RegexOptions.IgnoreCase).Match(fileName); var match = Regex.Match(fileName, expression, RegexOptions.IgnoreCase);
if (match.Success) if (match.Success)
{ {
if (!result.ChapterNumber.HasValue) if (!result.ChapterNumber.HasValue)

View File

@ -79,25 +79,25 @@ namespace Emby.Naming.AudioBook
{ {
if (group.Count() > 1 || haveChaptersOrPages) if (group.Count() > 1 || haveChaptersOrPages)
{ {
var ex = new List<AudioBookFileInfo>(); List<AudioBookFileInfo>? ex = null;
var alt = new List<AudioBookFileInfo>(); List<AudioBookFileInfo>? alt = null;
foreach (var audioFile in group) foreach (var audioFile in group)
{ {
var name = Path.GetFileNameWithoutExtension(audioFile.Path); var name = Path.GetFileNameWithoutExtension(audioFile.Path.AsSpan());
if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) || if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase)
name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) || || name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase)
name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase)) || name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase))
{ {
alt.Add(audioFile); (alt ??= new()).Add(audioFile);
} }
else else
{ {
ex.Add(audioFile); (ex ??= new()).Add(audioFile);
} }
} }
if (ex.Count > 0) if (ex is not null)
{ {
var extra = ex var extra = ex
.OrderBy(x => x.Container) .OrderBy(x => x.Container)
@ -108,7 +108,7 @@ namespace Emby.Naming.AudioBook
extras.AddRange(extra); extras.AddRange(extra);
} }
if (alt.Count > 0) if (alt is not null)
{ {
var alternatives = alt var alternatives = alt
.OrderBy(x => x.Container) .OrderBy(x => x.Container)

View File

@ -30,7 +30,7 @@ namespace Emby.Naming.AudioBook
AudioBookNameParserResult result = default; AudioBookNameParserResult result = default;
foreach (var expression in _options.AudioBookNamesExpressions) foreach (var expression in _options.AudioBookNamesExpressions)
{ {
var match = new Regex(expression, RegexOptions.IgnoreCase).Match(name); var match = Regex.Match(name, expression, RegexOptions.IgnoreCase);
if (match.Success) if (match.Success)
{ {
if (result.Name is null) if (result.Name is null)

View File

@ -14,7 +14,7 @@ namespace Emby.Naming.TV
/// Used for removing separators between words, i.e turns "The_show" into "The show" while /// Used for removing separators between words, i.e turns "The_show" into "The show" while
/// preserving namings like "S.H.O.W". /// preserving namings like "S.H.O.W".
/// </summary> /// </summary>
private static readonly Regex _seriesNameRegex = new Regex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))"); private static readonly Regex _seriesNameRegex = new Regex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))", RegexOptions.Compiled);
/// <summary> /// <summary>
/// Resolve information about series from path. /// Resolve information about series from path.

View File

@ -17,7 +17,7 @@ public class FileStackRule
/// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param> /// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param>
public FileStackRule(string token, bool isNumerical) public FileStackRule(string token, bool isNumerical)
{ {
_tokenRegex = new Regex(token, RegexOptions.IgnoreCase); _tokenRegex = new Regex(token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
IsNumerical = isNumerical; IsNumerical = isNumerical;
} }

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Emby.Naming.Common; using Emby.Naming.Common;
using Jellyfin.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
namespace Emby.Naming.Video namespace Emby.Naming.Video
@ -13,6 +14,8 @@ namespace Emby.Naming.Video
/// </summary> /// </summary>
public static class VideoListResolver public static class VideoListResolver
{ {
private static readonly Regex _resolutionRegex = new Regex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary> /// <summary>
/// Resolves alternative versions and extras from list of video files. /// Resolves alternative versions and extras from list of video files.
/// </summary> /// </summary>
@ -115,19 +118,34 @@ namespace Emby.Naming.Video
continue; continue;
} }
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions)) if (!IsEligibleForMultiVersion(folderName, video.Files[0].FileNameWithoutExtension, namingOptions))
{ {
return videos; return videos;
} }
if (folderName.Equals(Path.GetFileNameWithoutExtension(video.Files[0].Path.AsSpan()), StringComparison.Ordinal)) if (folderName.Equals(video.Files[0].FileNameWithoutExtension, StringComparison.Ordinal))
{ {
primary = video; primary = video;
} }
} }
// The list is created and overwritten in the caller, so we are allowed to do in-place sorting if (videos.Count > 1)
videos.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal)); {
var groups = videos.GroupBy(x => _resolutionRegex.IsMatch(x.Files[0].FileNameWithoutExtension)).ToList();
videos.Clear();
foreach (var group in groups)
{
if (group.Key)
{
videos.InsertRange(0, group.OrderByDescending(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator()));
}
else
{
videos.AddRange(group.OrderBy(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator()));
}
}
}
primary ??= videos[0]; primary ??= videos[0];
videos.Remove(primary); videos.Remove(primary);
@ -161,9 +179,8 @@ namespace Emby.Naming.Video
return true; return true;
} }
private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, string testFilePath, NamingOptions namingOptions) private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, ReadOnlySpan<char> testFilename, NamingOptions namingOptions)
{ {
var testFilename = Path.GetFileNameWithoutExtension(testFilePath.AsSpan());
if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{ {
return false; return false;

View File

@ -401,7 +401,7 @@ namespace Emby.Server.Implementations.Channels
} }
else else
{ {
results = new List<MediaSourceInfo>(); results = Enumerable.Empty<MediaSourceInfo>();
} }
return results return results

View File

@ -206,8 +206,7 @@ namespace Emby.Server.Implementations.Collections
throw new ArgumentException("No collection exists with the supplied Id"); throw new ArgumentException("No collection exists with the supplied Id");
} }
var list = new List<LinkedChild>(); List<BaseItem>? itemList = null;
var itemList = new List<BaseItem>();
var linkedChildrenList = collection.GetLinkedChildren(); var linkedChildrenList = collection.GetLinkedChildren();
var currentLinkedChildrenIds = linkedChildrenList.Select(i => i.Id).ToList(); var currentLinkedChildrenIds = linkedChildrenList.Select(i => i.Id).ToList();
@ -223,18 +222,23 @@ namespace Emby.Server.Implementations.Collections
if (!currentLinkedChildrenIds.Contains(id)) if (!currentLinkedChildrenIds.Contains(id))
{ {
itemList.Add(item); (itemList ??= new()).Add(item);
list.Add(LinkedChild.Create(item));
linkedChildrenList.Add(item); linkedChildrenList.Add(item);
} }
} }
if (list.Count > 0) if (itemList is not null)
{ {
LinkedChild[] newChildren = new LinkedChild[collection.LinkedChildren.Length + list.Count]; var originalLen = collection.LinkedChildren.Length;
var newItemCount = itemList.Count;
LinkedChild[] newChildren = new LinkedChild[originalLen + newItemCount];
collection.LinkedChildren.CopyTo(newChildren, 0); collection.LinkedChildren.CopyTo(newChildren, 0);
list.CopyTo(newChildren, collection.LinkedChildren.Length); for (int i = 0; i < newItemCount; i++)
{
newChildren[originalLen + i] = LinkedChild.Create(itemList[i]);
}
collection.LinkedChildren = newChildren; collection.LinkedChildren = newChildren;
collection.UpdateRatingToItems(linkedChildrenList); collection.UpdateRatingToItems(linkedChildrenList);

View File

@ -586,7 +586,7 @@ namespace Emby.Server.Implementations.Data
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// <paramref name="items"/> or <paramref name="cancellationToken"/> is <c>null</c>. /// <paramref name="items"/> or <paramref name="cancellationToken"/> is <c>null</c>.
/// </exception> /// </exception>
public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken) public void SaveItems(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken)
{ {
ArgumentNullException.ThrowIfNull(items); ArgumentNullException.ThrowIfNull(items);
@ -594,9 +594,11 @@ namespace Emby.Server.Implementations.Data
CheckDisposed(); CheckDisposed();
var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>(); var itemsLen = items.Count;
foreach (var item in items) var tuples = new ValueTuple<BaseItem, List<Guid>, BaseItem, string, List<string>>[itemsLen];
for (int i = 0; i < itemsLen; i++)
{ {
var item = items[i];
var ancestorIds = item.SupportsAncestors ? var ancestorIds = item.SupportsAncestors ?
item.GetAncestorIds().Distinct().ToList() : item.GetAncestorIds().Distinct().ToList() :
null; null;
@ -606,7 +608,7 @@ namespace Emby.Server.Implementations.Data
var userdataKey = item.GetUserDataKeys().FirstOrDefault(); var userdataKey = item.GetUserDataKeys().FirstOrDefault();
var inheritedTags = item.GetInheritedTags(); var inheritedTags = item.GetInheritedTags();
tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags)); tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags);
} }
using (var connection = GetConnection()) using (var connection = GetConnection())
@ -3202,7 +3204,8 @@ namespace Emby.Server.Implementations.Data
return IsAlphaNumeric(value); return IsAlphaNumeric(value);
} }
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement) #nullable enable
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement? statement)
{ {
if (query.IsResumable ?? false) if (query.IsResumable ?? false)
{ {
@ -3677,7 +3680,6 @@ namespace Emby.Server.Implementations.Data
if (statement is not null) if (statement is not null)
{ {
nameContains = FixUnicodeChars(nameContains); nameContains = FixUnicodeChars(nameContains);
statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%"); statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%");
} }
} }
@ -3803,13 +3805,8 @@ namespace Emby.Server.Implementations.Data
foreach (var artistId in query.ArtistIds) foreach (var artistId in query.ArtistIds)
{ {
var paramName = "@ArtistIds" + index; var paramName = "@ArtistIds" + index;
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
if (statement is not null) statement?.TryBind(paramName, artistId);
{
statement.TryBind(paramName, artistId);
}
index++; index++;
} }
@ -3824,13 +3821,8 @@ namespace Emby.Server.Implementations.Data
foreach (var artistId in query.AlbumArtistIds) foreach (var artistId in query.AlbumArtistIds)
{ {
var paramName = "@ArtistIds" + index; var paramName = "@ArtistIds" + index;
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))"); clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
if (statement is not null) statement?.TryBind(paramName, artistId);
{
statement.TryBind(paramName, artistId);
}
index++; index++;
} }
@ -3845,13 +3837,8 @@ namespace Emby.Server.Implementations.Data
foreach (var artistId in query.ContributingArtistIds) foreach (var artistId in query.ContributingArtistIds)
{ {
var paramName = "@ArtistIds" + index; var paramName = "@ArtistIds" + index;
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))"); clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))");
if (statement is not null) statement?.TryBind(paramName, artistId);
{
statement.TryBind(paramName, artistId);
}
index++; index++;
} }
@ -3866,13 +3853,8 @@ namespace Emby.Server.Implementations.Data
foreach (var albumId in query.AlbumIds) foreach (var albumId in query.AlbumIds)
{ {
var paramName = "@AlbumIds" + index; var paramName = "@AlbumIds" + index;
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")"); clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
if (statement is not null) statement?.TryBind(paramName, albumId);
{
statement.TryBind(paramName, albumId);
}
index++; index++;
} }
@ -3887,13 +3869,8 @@ namespace Emby.Server.Implementations.Data
foreach (var artistId in query.ExcludeArtistIds) foreach (var artistId in query.ExcludeArtistIds)
{ {
var paramName = "@ExcludeArtistId" + index; var paramName = "@ExcludeArtistId" + index;
clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
if (statement is not null) statement?.TryBind(paramName, artistId);
{
statement.TryBind(paramName, artistId);
}
index++; index++;
} }
@ -3908,13 +3885,8 @@ namespace Emby.Server.Implementations.Data
foreach (var genreId in query.GenreIds) foreach (var genreId in query.GenreIds)
{ {
var paramName = "@GenreId" + index; var paramName = "@GenreId" + index;
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))"); clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
if (statement is not null) statement?.TryBind(paramName, genreId);
{
statement.TryBind(paramName, genreId);
}
index++; index++;
} }
@ -3929,11 +3901,7 @@ namespace Emby.Server.Implementations.Data
foreach (var item in query.Genres) foreach (var item in query.Genres)
{ {
clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)"); clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)");
if (statement is not null) statement?.TryBind("@Genre" + index, GetCleanValue(item));
{
statement.TryBind("@Genre" + index, GetCleanValue(item));
}
index++; index++;
} }
@ -3948,11 +3916,7 @@ namespace Emby.Server.Implementations.Data
foreach (var item in tags) foreach (var item in tags)
{ {
clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)"); clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
if (statement is not null) statement?.TryBind("@Tag" + index, GetCleanValue(item));
{
statement.TryBind("@Tag" + index, GetCleanValue(item));
}
index++; index++;
} }
@ -3967,11 +3931,7 @@ namespace Emby.Server.Implementations.Data
foreach (var item in excludeTags) foreach (var item in excludeTags)
{ {
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)"); clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
if (statement is not null) statement?.TryBind("@ExcludeTag" + index, GetCleanValue(item));
{
statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
}
index++; index++;
} }
@ -3986,14 +3946,8 @@ namespace Emby.Server.Implementations.Data
foreach (var studioId in query.StudioIds) foreach (var studioId in query.StudioIds)
{ {
var paramName = "@StudioId" + index; var paramName = "@StudioId" + index;
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))"); clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
statement?.TryBind(paramName, studioId);
if (statement is not null)
{
statement.TryBind(paramName, studioId);
}
index++; index++;
} }
@ -4008,11 +3962,7 @@ namespace Emby.Server.Implementations.Data
foreach (var item in query.OfficialRatings) foreach (var item in query.OfficialRatings)
{ {
clauses.Add("OfficialRating=@OfficialRating" + index); clauses.Add("OfficialRating=@OfficialRating" + index);
if (statement is not null) statement?.TryBind("@OfficialRating" + index, item);
{
statement.TryBind("@OfficialRating" + index, item);
}
index++; index++;
} }
@ -4020,34 +3970,96 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(clause); whereClauses.Add(clause);
} }
var ratingClauseBuilder = new StringBuilder("(");
if (query.HasParentalRating ?? false)
{
ratingClauseBuilder.Append("InheritedParentalRatingValue not null");
if (query.MinParentalRating.HasValue) if (query.MinParentalRating.HasValue)
{ {
whereClauses.Add("InheritedParentalRatingValue>=@MinParentalRating"); ratingClauseBuilder.Append(" AND InheritedParentalRatingValue >= @MinParentalRating");
if (statement is not null) statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
{
statement.TryBind("@MinParentalRating", query.MinParentalRating.Value);
}
} }
if (query.MaxParentalRating.HasValue) if (query.MaxParentalRating.HasValue)
{ {
whereClauses.Add("InheritedParentalRatingValue<=@MaxParentalRating"); ratingClauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
}
}
else if (query.BlockUnratedItems.Length > 0)
{
var paramName = "@UnratedType";
var index = 0;
string blockedUnratedItems = string.Join(',', query.BlockUnratedItems.Select(_ => paramName + index++));
ratingClauseBuilder.Append("(InheritedParentalRatingValue is null AND UnratedType not in (" + blockedUnratedItems + "))");
if (statement is not null) if (statement is not null)
{ {
statement.TryBind("@MaxParentalRating", query.MaxParentalRating.Value); for (var ind = 0; ind < query.BlockUnratedItems.Length; ind++)
{
statement.TryBind(paramName + ind, query.BlockUnratedItems[ind].ToString());
} }
} }
if (query.HasParentalRating.HasValue) if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
{ {
if (query.HasParentalRating.Value) ratingClauseBuilder.Append(" OR (");
{
whereClauses.Add("InheritedParentalRatingValue > 0");
} }
else
if (query.MinParentalRating.HasValue)
{ {
whereClauses.Add("InheritedParentalRatingValue = 0"); ratingClauseBuilder.Append("InheritedParentalRatingValue >= @MinParentalRating");
statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
} }
if (query.MaxParentalRating.HasValue)
{
if (query.MinParentalRating.HasValue)
{
ratingClauseBuilder.Append(" AND ");
}
ratingClauseBuilder.Append("InheritedParentalRatingValue <= @MaxParentalRating");
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
}
if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
{
ratingClauseBuilder.Append(")");
}
if (!(query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue))
{
ratingClauseBuilder.Append(" OR InheritedParentalRatingValue not null");
}
}
else if (query.MinParentalRating.HasValue)
{
ratingClauseBuilder.Append("InheritedParentalRatingValue is null OR (InheritedParentalRatingValue >= @MinParentalRating");
statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
if (query.MaxParentalRating.HasValue)
{
ratingClauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
}
ratingClauseBuilder.Append(")");
}
else if (query.MaxParentalRating.HasValue)
{
ratingClauseBuilder.Append("InheritedParentalRatingValue is null OR InheritedParentalRatingValue <= @MaxParentalRating");
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
}
else if (!query.HasParentalRating ?? false)
{
ratingClauseBuilder.Append("InheritedParentalRatingValue is null");
}
var ratingClauseString = ratingClauseBuilder.ToString();
if (!string.Equals(ratingClauseString, "(", StringComparison.OrdinalIgnoreCase))
{
whereClauses.Add(ratingClauseString + ")");
} }
if (query.HasOfficialRating.HasValue) if (query.HasOfficialRating.HasValue)
@ -4089,37 +4101,25 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage)) if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
{ {
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)"); whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)");
if (statement is not null) statement?.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
{
statement.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
}
} }
if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage)) if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
{ {
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)"); whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)");
if (statement is not null) statement?.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
{
statement.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
}
} }
if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage)) if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
{ {
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)"); whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)");
if (statement is not null) statement?.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
{
statement.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
}
} }
if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage)) if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
{ {
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)"); whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)");
if (statement is not null) statement?.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
{
statement.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
}
} }
if (query.HasSubtitles.HasValue) if (query.HasSubtitles.HasValue)
@ -4169,15 +4169,11 @@ namespace Emby.Server.Implementations.Data
if (query.Years.Length == 1) if (query.Years.Length == 1)
{ {
whereClauses.Add("ProductionYear=@Years"); whereClauses.Add("ProductionYear=@Years");
if (statement is not null) statement?.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
{
statement.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
}
} }
else if (query.Years.Length > 1) else if (query.Years.Length > 1)
{ {
var val = string.Join(',', query.Years); var val = string.Join(',', query.Years);
whereClauses.Add("ProductionYear in (" + val + ")"); whereClauses.Add("ProductionYear in (" + val + ")");
} }
@ -4185,10 +4181,7 @@ namespace Emby.Server.Implementations.Data
if (isVirtualItem.HasValue) if (isVirtualItem.HasValue)
{ {
whereClauses.Add("IsVirtualItem=@IsVirtualItem"); whereClauses.Add("IsVirtualItem=@IsVirtualItem");
if (statement is not null) statement?.TryBind("@IsVirtualItem", isVirtualItem.Value);
{
statement.TryBind("@IsVirtualItem", isVirtualItem.Value);
}
} }
if (query.IsSpecialSeason.HasValue) if (query.IsSpecialSeason.HasValue)
@ -4219,31 +4212,22 @@ namespace Emby.Server.Implementations.Data
if (queryMediaTypes.Length == 1) if (queryMediaTypes.Length == 1)
{ {
whereClauses.Add("MediaType=@MediaTypes"); whereClauses.Add("MediaType=@MediaTypes");
if (statement is not null) statement?.TryBind("@MediaTypes", queryMediaTypes[0]);
{
statement.TryBind("@MediaTypes", queryMediaTypes[0]);
}
} }
else if (queryMediaTypes.Length > 1) else if (queryMediaTypes.Length > 1)
{ {
var val = string.Join(',', queryMediaTypes.Select(i => "'" + i + "'")); var val = string.Join(',', queryMediaTypes.Select(i => "'" + i + "'"));
whereClauses.Add("MediaType in (" + val + ")"); whereClauses.Add("MediaType in (" + val + ")");
} }
if (query.ItemIds.Length > 0) if (query.ItemIds.Length > 0)
{ {
var includeIds = new List<string>(); var includeIds = new List<string>();
var index = 0; var index = 0;
foreach (var id in query.ItemIds) foreach (var id in query.ItemIds)
{ {
includeIds.Add("Guid = @IncludeId" + index); includeIds.Add("Guid = @IncludeId" + index);
if (statement is not null) statement?.TryBind("@IncludeId" + index, id);
{
statement.TryBind("@IncludeId" + index, id);
}
index++; index++;
} }
@ -4253,16 +4237,11 @@ namespace Emby.Server.Implementations.Data
if (query.ExcludeItemIds.Length > 0) if (query.ExcludeItemIds.Length > 0)
{ {
var excludeIds = new List<string>(); var excludeIds = new List<string>();
var index = 0; var index = 0;
foreach (var id in query.ExcludeItemIds) foreach (var id in query.ExcludeItemIds)
{ {
excludeIds.Add("Guid <> @ExcludeId" + index); excludeIds.Add("Guid <> @ExcludeId" + index);
if (statement is not null) statement?.TryBind("@ExcludeId" + index, id);
{
statement.TryBind("@ExcludeId" + index, id);
}
index++; index++;
} }
@ -4283,11 +4262,7 @@ namespace Emby.Server.Implementations.Data
var paramName = "@ExcludeProviderId" + index; var paramName = "@ExcludeProviderId" + index;
excludeIds.Add("(ProviderIds is null or ProviderIds not like " + paramName + ")"); excludeIds.Add("(ProviderIds is null or ProviderIds not like " + paramName + ")");
if (statement is not null) statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
{
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
}
index++; index++;
break; break;
@ -4312,7 +4287,7 @@ namespace Emby.Server.Implementations.Data
} }
// TODO this seems to be an idea for a better schema where ProviderIds are their own table // TODO this seems to be an idea for a better schema where ProviderIds are their own table
// buut this is not implemented // but this is not implemented
// hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")"); // hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
// TODO this is a really BAD way to do it since the pair: // TODO this is a really BAD way to do it since the pair:
@ -4326,11 +4301,7 @@ namespace Emby.Server.Implementations.Data
hasProviderIds.Add("ProviderIds like " + paramName); hasProviderIds.Add("ProviderIds like " + paramName);
// this replaces the placeholder with a value, here: %key=val% // this replaces the placeholder with a value, here: %key=val%
if (statement is not null) statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
{
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
}
index++; index++;
break; break;
@ -4407,11 +4378,7 @@ namespace Emby.Server.Implementations.Data
if (query.AncestorIds.Length == 1) if (query.AncestorIds.Length == 1)
{ {
whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)"); whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)");
statement?.TryBind("@AncestorId", query.AncestorIds[0]);
if (statement is not null)
{
statement.TryBind("@AncestorId", query.AncestorIds[0]);
}
} }
if (query.AncestorIds.Length > 1) if (query.AncestorIds.Length > 1)
@ -4424,39 +4391,13 @@ namespace Emby.Server.Implementations.Data
{ {
var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey"; var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause)); whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
if (statement is not null) statement?.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
{
statement.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
}
} }
if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey)) if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey))
{ {
whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey"); whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey");
statement?.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey);
if (statement is not null)
{
statement.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey);
}
}
if (query.BlockUnratedItems.Length == 1)
{
whereClauses.Add("(InheritedParentalRatingValue > 0 or UnratedType <> @UnratedType)");
if (statement is not null)
{
statement.TryBind("@UnratedType", query.BlockUnratedItems[0].ToString());
}
}
if (query.BlockUnratedItems.Length > 1)
{
var inClause = string.Join(',', query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'"));
whereClauses.Add(
string.Format(
CultureInfo.InvariantCulture,
"(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))",
inClause));
} }
if (query.ExcludeInheritedTags.Length > 0) if (query.ExcludeInheritedTags.Length > 0)
@ -4605,6 +4546,7 @@ namespace Emby.Server.Implementations.Data
return whereClauses; return whereClauses;
} }
#nullable disable
/// <summary> /// <summary>
/// Formats a where clause for the specified provider. /// Formats a where clause for the specified provider.

View File

@ -83,22 +83,23 @@ namespace Emby.Server.Implementations.Dto
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
{ {
var returnItems = new BaseItemDto[items.Count]; var accessibleItems = user is null ? items : items.Where(x => x.IsVisible(user)).ToList();
var programTuples = new List<(BaseItem, BaseItemDto)>(); var returnItems = new BaseItemDto[accessibleItems.Count];
var channelTuples = new List<(BaseItemDto, LiveTvChannel)>(); List<(BaseItem, BaseItemDto)> programTuples = null;
List<(BaseItemDto, LiveTvChannel)> channelTuples = null;
for (int index = 0; index < items.Count; index++) for (int index = 0; index < accessibleItems.Count; index++)
{ {
var item = items[index]; var item = accessibleItems[index];
var dto = GetBaseItemDtoInternal(item, options, user, owner); var dto = GetBaseItemDtoInternal(item, options, user, owner);
if (item is LiveTvChannel tvChannel) if (item is LiveTvChannel tvChannel)
{ {
channelTuples.Add((dto, tvChannel)); (channelTuples ??= new()).Add((dto, tvChannel));
} }
else if (item is LiveTvProgram) else if (item is LiveTvProgram)
{ {
programTuples.Add((item, dto)); (programTuples ??= new()).Add((item, dto));
} }
if (item is IItemByName byName) if (item is IItemByName byName)
@ -121,12 +122,12 @@ namespace Emby.Server.Implementations.Dto
returnItems[index] = dto; returnItems[index] = dto;
} }
if (programTuples.Count > 0) if (programTuples is not null)
{ {
LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult(); LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
} }
if (channelTuples.Count > 0) if (channelTuples is not null)
{ {
LivetvManager.AddChannelInfo(channelTuples, options, user); LivetvManager.AddChannelInfo(channelTuples, options, user);
} }

View File

@ -356,8 +356,8 @@ namespace Emby.Server.Implementations.Library
} }
var children = item.IsFolder var children = item.IsFolder
? ((Folder)item).GetRecursiveChildren(false).ToList() ? ((Folder)item).GetRecursiveChildren(false)
: new List<BaseItem>(); : Enumerable.Empty<BaseItem>();
foreach (var metadataPath in GetMetadataPaths(item, children)) foreach (var metadataPath in GetMetadataPaths(item, children))
{ {
@ -1253,7 +1253,7 @@ namespace Emby.Server.Implementations.Library
var parent = GetItemById(query.ParentId); var parent = GetItemById(query.ParentId);
if (parent is not null) if (parent is not null)
{ {
SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); SetTopParentIdsOrAncestors(query, new[] { parent });
} }
} }
@ -1277,7 +1277,7 @@ namespace Emby.Server.Implementations.Library
var parent = GetItemById(query.ParentId); var parent = GetItemById(query.ParentId);
if (parent is not null) if (parent is not null)
{ {
SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); SetTopParentIdsOrAncestors(query, new[] { parent });
} }
} }
@ -1435,7 +1435,7 @@ namespace Emby.Server.Implementations.Library
var parent = GetItemById(query.ParentId); var parent = GetItemById(query.ParentId);
if (parent is not null) if (parent is not null)
{ {
SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); SetTopParentIdsOrAncestors(query, new[] { parent });
} }
} }
@ -1455,7 +1455,7 @@ namespace Emby.Server.Implementations.Library
_itemRepository.GetItemList(query)); _itemRepository.GetItemList(query));
} }
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents) private void SetTopParentIdsOrAncestors(InternalItemsQuery query, IReadOnlyCollection<BaseItem> parents)
{ {
if (parents.All(i => i is ICollectionFolder || i is UserView)) if (parents.All(i => i is ICollectionFolder || i is UserView))
{ {
@ -1602,7 +1602,7 @@ namespace Emby.Server.Implementations.Library
{ {
_logger.LogError(ex, "Error getting intros"); _logger.LogError(ex, "Error getting intros");
return new List<IntroInfo>(); return Enumerable.Empty<IntroInfo>();
} }
} }
@ -2876,7 +2876,7 @@ namespace Emby.Server.Implementations.Library
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken) private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
{ {
var personsToSave = new List<BaseItem>(); List<BaseItem> personsToSave = null;
foreach (var person in people) foreach (var person in people)
{ {
@ -2918,12 +2918,12 @@ namespace Emby.Server.Implementations.Library
if (saveEntity) if (saveEntity)
{ {
personsToSave.Add(personEntity); (personsToSave ??= new()).Add(personEntity);
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
} }
} }
if (personsToSave.Count > 0) if (personsToSave is not null)
{ {
CreateItems(personsToSave, null, CancellationToken.None); CreateItems(personsToSave, null, CancellationToken.None);
} }
@ -3085,22 +3085,19 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
var removeList = new List<NameValuePair>(); List<NameValuePair> removeList = null;
foreach (var contentType in _configurationManager.Configuration.ContentTypes) foreach (var contentType in _configurationManager.Configuration.ContentTypes)
{ {
if (string.IsNullOrWhiteSpace(contentType.Name)) if (string.IsNullOrWhiteSpace(contentType.Name)
{ || _fileSystem.AreEqual(path, contentType.Name)
removeList.Add(contentType);
}
else if (_fileSystem.AreEqual(path, contentType.Name)
|| _fileSystem.ContainsSubPath(path, contentType.Name)) || _fileSystem.ContainsSubPath(path, contentType.Name))
{ {
removeList.Add(contentType); (removeList ??= new()).Add(contentType);
} }
} }
if (removeList.Count > 0) if (removeList is not null)
{ {
_configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes _configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes
.Except(removeList) .Except(removeList)

View File

@ -158,7 +158,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName) private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName)
{ {
var files = new List<FileSystemMetadata>(); var files = new List<FileSystemMetadata>();
var items = new List<BaseItem>();
var leftOver = new List<FileSystemMetadata>(); var leftOver = new List<FileSystemMetadata>();
// Loop through each child file/folder and see if we find a video // Loop through each child file/folder and see if we find a video
@ -180,7 +179,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var result = new MultiItemResolverResult var result = new MultiItemResolverResult
{ {
ExtraFiles = leftOver, ExtraFiles = leftOver,
Items = items Items = new List<BaseItem>()
}; };
var isInMixedFolder = resolverResult.Count > 1 || (parent is not null && parent.IsTopParent); var isInMixedFolder = resolverResult.Count > 1 || (parent is not null && parent.IsTopParent);

View File

@ -286,7 +286,7 @@ namespace Emby.Server.Implementations.Library
if (parents.Count == 0) if (parents.Count == 0)
{ {
return new List<BaseItem>(); return Array.Empty<BaseItem>();
} }
if (includeItemTypes.Length == 0) if (includeItemTypes.Length == 0)

View File

@ -13,8 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public LegacyHdHomerunChannelCommands(string url) public LegacyHdHomerunChannelCommands(string url)
{ {
// parse url for channel and program // parse url for channel and program
var regExp = new Regex(@"\/ch([0-9]+)-?([0-9]*)"); var match = Regex.Match(url, @"\/ch([0-9]+)-?([0-9]*)");
var match = regExp.Match(url);
if (match.Success) if (match.Success)
{ {
_channel = match.Groups[1].Value; _channel = match.Groups[1].Value;

View File

@ -308,8 +308,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var reg = new Regex(@"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase); var matches = Regex.Matches(line, @"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase);
var matches = reg.Matches(line);
remaining = line; remaining = line;

View File

@ -31,7 +31,7 @@
"ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca",
"LabelIpAddressValue": "Dirección IP: {0}", "LabelIpAddressValue": "Dirección IP: {0}",
"LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}",
"Latest": "Últimos", "Latest": "Último contenido en",
"MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin", "MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin",
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada", "MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada",

View File

@ -67,5 +67,11 @@
"Plugin": "प्लग-इन", "Plugin": "प्लग-इन",
"Playlists": "प्लेलिस्ट", "Playlists": "प्लेलिस्ट",
"Photos": "तस्वीरें", "Photos": "तस्वीरें",
"External": "बाहरी" "External": "बाहरी",
"PluginUpdatedWithName": "{0} अपडेट हुए",
"ScheduledTaskStartedWithName": "{0} शुरू हुए",
"Songs": "गाने",
"UserStartedPlayingItemWithValues": "{0} {2} पर {1} खेल रहे हैं",
"UserStoppedPlayingItemWithValues": "{0} ने {2} पर {1} खेलना खत्म किया",
"StartupEmbyServerIsLoading": "जेलीफ़िन सर्वर लोड हो रहा है। कृपया शीघ्र ही पुन: प्रयास करें।"
} }

View File

@ -95,13 +95,13 @@
"TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar ontbrekende ondertiteling gebaseerd op metadataconfiguratie.", "TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar ontbrekende ondertiteling gebaseerd op metadataconfiguratie.",
"TaskDownloadMissingSubtitles": "Ontbrekende ondertiteling downloaden", "TaskDownloadMissingSubtitles": "Ontbrekende ondertiteling downloaden",
"TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.", "TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
"TaskRefreshChannels": "Vernieuw kanalen", "TaskRefreshChannels": "Kanalen vernieuwen",
"TaskCleanTranscodeDescription": "Verwijdert transcode bestanden ouder dan 1 dag.", "TaskCleanTranscodeDescription": "Verwijdert transcode bestanden ouder dan 1 dag.",
"TaskCleanLogs": "Logboekmap opschonen", "TaskCleanLogs": "Logboekmap opschonen",
"TaskCleanTranscode": "Transcoderingsmap opschonen", "TaskCleanTranscode": "Transcoderingsmap opschonen",
"TaskUpdatePluginsDescription": "Downloadt en installeert updates van plug-ins waarvoor automatisch bijwerken is ingeschakeld.", "TaskUpdatePluginsDescription": "Downloadt en installeert updates van plug-ins waarvoor automatisch bijwerken is ingeschakeld.",
"TaskUpdatePlugins": "Plug-ins bijwerken", "TaskUpdatePlugins": "Plug-ins bijwerken",
"TaskRefreshPeopleDescription": "Update metadata voor acteurs en regisseurs in de media bibliotheek.", "TaskRefreshPeopleDescription": "Updatet metadata voor acteurs en regisseurs in je mediabibliotheek.",
"TaskRefreshPeople": "Personen vernieuwen", "TaskRefreshPeople": "Personen vernieuwen",
"TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.", "TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
"TaskRefreshLibraryDescription": "Scant de mediabibliotheek op nieuwe bestanden en vernieuwt de metadata.", "TaskRefreshLibraryDescription": "Scant de mediabibliotheek op nieuwe bestanden en vernieuwt de metadata.",

View File

@ -86,7 +86,7 @@
"Shows": "Шоу", "Shows": "Шоу",
"ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити", "ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити",
"ScheduledTaskStartedWithName": "{0} розпочато", "ScheduledTaskStartedWithName": "{0} розпочато",
"ScheduledTaskFailedWithName": "Помилка {0}", "ScheduledTaskFailedWithName": "{0} незавершено, збій",
"ProviderValue": "Постачальник: {0}", "ProviderValue": "Постачальник: {0}",
"PluginUpdatedWithName": "{0} оновлено", "PluginUpdatedWithName": "{0} оновлено",
"PluginUninstalledWithName": "{0} видалено", "PluginUninstalledWithName": "{0} видалено",

View File

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Localization
private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt"; private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt";
private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json"; private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json";
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated", "nr" };
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger<LocalizationManager> _logger; private readonly ILogger<LocalizationManager> _logger;
@ -86,12 +87,10 @@ namespace Emby.Server.Implementations.Localization
var name = parts[0]; var name = parts[0];
dict.Add(name, new ParentalRating(name, value)); dict.Add(name, new ParentalRating(name, value));
} }
#if DEBUG
else else
{ {
_logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode); _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
} }
#endif
} }
_allParentalRatings[countryCode] = dict; _allParentalRatings[countryCode] = dict;
@ -184,7 +183,56 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ParentalRating> GetParentalRatings() public IEnumerable<ParentalRating> GetParentalRatings()
=> GetParentalRatingsDictionary().Values; {
var ratings = GetParentalRatingsDictionary().Values.ToList();
// Add common ratings to ensure them being available for selection.
// Based on the US rating system due to it being the main source of rating in the metadata providers
// Minimum rating possible
if (!ratings.Any(x => x.Value == 0))
{
ratings.Add(new ParentalRating("Approved", 0));
}
// Matches PG (this has different age restrictions depending on country)
if (!ratings.Any(x => x.Value == 10))
{
ratings.Add(new ParentalRating("10", 10));
}
// Matches PG-13
if (!ratings.Any(x => x.Value == 13))
{
ratings.Add(new ParentalRating("13", 13));
}
// Matches TV-14
if (!ratings.Any(x => x.Value == 14))
{
ratings.Add(new ParentalRating("14", 14));
}
// Catchall if max rating of country is less than 21
// Using 21 instead of 18 to be sure to allow access to all rated content except adult and banned
if (!ratings.Any(x => x.Value >= 21))
{
ratings.Add(new ParentalRating("21", 21));
}
// A lot of countries don't excplicitly have a seperate rating for adult content
if (!ratings.Any(x => x.Value == 1000))
{
ratings.Add(new ParentalRating("XXX", 1000));
}
// A lot of countries don't excplicitly have a seperate rating for banned content
if (!ratings.Any(x => x.Value == 1001))
{
ratings.Add(new ParentalRating("Banned", 1001));
}
return ratings.OrderBy(r => r.Value);
}
/// <summary> /// <summary>
/// Gets the parental ratings dictionary. /// Gets the parental ratings dictionary.
@ -194,6 +242,7 @@ namespace Emby.Server.Implementations.Localization
{ {
var countryCode = _configurationManager.Configuration.MetadataCountryCode; var countryCode = _configurationManager.Configuration.MetadataCountryCode;
// Fall back to US ratings if no country code is specified or country code does not exist.
if (string.IsNullOrEmpty(countryCode)) if (string.IsNullOrEmpty(countryCode))
{ {
countryCode = "us"; countryCode = "us";
@ -205,15 +254,15 @@ namespace Emby.Server.Implementations.Localization
} }
/// <summary> /// <summary>
/// Gets the ratings. /// Gets the ratings for a country.
/// </summary> /// </summary>
/// <param name="countryCode">The country code.</param> /// <param name="countryCode">The country code.</param>
/// <returns>The ratings.</returns> /// <returns>The ratings.</returns>
private Dictionary<string, ParentalRating>? GetRatings(string countryCode) private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
{ {
_allParentalRatings.TryGetValue(countryCode, out var value); _allParentalRatings.TryGetValue(countryCode, out var countryValue);
return value; return countryValue;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -221,12 +270,14 @@ namespace Emby.Server.Implementations.Localization
{ {
ArgumentException.ThrowIfNullOrEmpty(rating); ArgumentException.ThrowIfNullOrEmpty(rating);
// Handle unrated content
if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase)) if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase))
{ {
return null; return null;
} }
// Fairly common for some users to have "Rated R" in their rating field // Fairly common for some users to have "Rated R" in their rating field
rating = rating.Replace("Rated :", string.Empty, StringComparison.OrdinalIgnoreCase);
rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase); rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase);
var ratingsDictionary = GetParentalRatingsDictionary(); var ratingsDictionary = GetParentalRatingsDictionary();
@ -246,18 +297,17 @@ namespace Emby.Server.Implementations.Localization
} }
// Try splitting by : to handle "Germany: FSK 18" // Try splitting by : to handle "Germany: FSK 18"
var index = rating.IndexOf(':', StringComparison.Ordinal); if (rating.Contains(':', StringComparison.OrdinalIgnoreCase))
if (index != -1)
{ {
var trimmedRating = rating.AsSpan(index).TrimStart(':').Trim(); return GetRatingLevel(rating.AsSpan().RightPart(':').ToString());
}
if (!trimmedRating.IsEmpty)
{ // Remove prefix country code to handle "DE-18"
return GetRatingLevel(trimmedRating.ToString()); if (rating.Contains('-', StringComparison.OrdinalIgnoreCase))
} {
return GetRatingLevel(rating.AsSpan().RightPart('-').ToString());
} }
// TODO: Further improve by normalizing out all spaces and dashes
return null; return null;
} }

View File

@ -0,0 +1,11 @@
E,0
EC,0
T,7
M,18
AO,18
UR,18
RP,18
X,1000
XX,1000
XXX,1000
XXXX,1000
1 E 0
2 EC 0
3 T 7
4 M 18
5 AO 18
6 UR 18
7 RP 18
8 X 1000
9 XX 1000
10 XXX 1000
11 XXXX 1000

View File

@ -1,7 +1,13 @@
AU-G,1 Exempt,0
AU-PG,5 G,0
AU-M,6 7+,7
AU-MA15+,7 M,15
AU-R18+,9 MA,15
AU-X18+,10 MA15+,15
AU-RC,11 PG,16
16+,16
R,18
R18+,18
X18+,18
18+,18
X,1000

1 AU-G Exempt 1 0
2 AU-PG G 5 0
3 AU-M 7+ 6 7
4 AU-MA15+ M 7 15
5 AU-R18+ MA 9 15
6 AU-X18+ MA15+ 10 15
7 AU-RC PG 11 16
8 16+ 16
9 R 18
10 R18+ 18
11 X18+ 18
12 18+ 18
13 X 1000

View File

@ -1,6 +1,11 @@
BE-AL,1 AL,0
BE-MG6,2 KT,0
BE-6,3 TOUS,0
BE-9,5 MG6,6
BE-12,6 6,6
BE-16,8 9,9
KNT,12
12,12
14,14
16,16
18,18

1 BE-AL AL 1 0
2 BE-MG6 KT 2 0
3 BE-6 TOUS 3 0
4 BE-9 MG6 5 6
5 BE-12 6 6 6
6 BE-16 9 8 9
7 KNT 12
8 12 12
9 14 14
10 16 16
11 18 18

View File

@ -1,6 +1,8 @@
BR-L,1 Livre,0
BR-10,5 L,0
BR-12,7 ER,9
BR-14,8 10,10
BR-16,8 12,12
BR-18,9 14,14
16,16
18,18

1 BR-L Livre 1 0
2 BR-10 L 5 0
3 BR-12 ER 7 9
4 BR-14 10 8 10
5 BR-16 12 8 12
6 BR-18 14 9 14
7 16 16
8 18 18

View File

@ -1,6 +1,20 @@
CA-G,1 E,0
CA-PG,5 G,0
CA-14A,7 TV-Y,0
CA-A,8 TV-G,0
CA-18A,9 TV-Y7,7
CA-R,10 TV-Y7-FV,7
PG,9
TV-PG,9
PG-13,13
13+,13
TV-14,14
14A,14
16+,16
NC-17,17
R,18
TV-MA,18
18A,18
18+,18
A,1000
Prohibited,1001

1 CA-G E 1 0
2 CA-PG G 5 0
3 CA-14A TV-Y 7 0
4 CA-A TV-G 8 0
5 CA-18A TV-Y7 9 7
6 CA-R TV-Y7-FV 10 7
7 PG 9
8 TV-PG 9
9 PG-13 13
10 13+ 13
11 TV-14 14
12 14A 14
13 16+ 16
14 NC-17 17
15 R 18
16 TV-MA 18
17 18A 18
18 18+ 18
19 A 1000
20 Prohibited 1001

View File

@ -1,8 +1,7 @@
CO-T,1 T,0
CO-7,5 7,7
CO-12,7 12,12
CO-15,8 15,15
CO-18,10 18,18
CO-X,100 X,1000
CO-BANNED,15 Prohibited,1001
CO-E,15

1 CO-T T 1 0
2 CO-7 7 5 7
3 CO-12 12 7 12
4 CO-15 15 8 15
5 CO-18 18 10 18
6 CO-X X 100 1000
7 CO-BANNED Prohibited 15 1001
CO-E 15

View File

@ -1,10 +1,12 @@
DE-0,1 Educational,0
FSK-0,1 Infoprogramm,0
DE-6,5 FSK-0,0
FSK-6,5 0,0
DE-12,7 FSK-6,6
FSK-12,7 6,6
DE-16,8 FSK-12,12
FSK-16,8 12,12
DE-18,9 FSK-16,16
FSK-18,9 16,16
FSK-18,18
18,18

1 DE-0 Educational 1 0
2 FSK-0 Infoprogramm 1 0
3 DE-6 FSK-0 5 0
4 FSK-6 0 5 0
5 DE-12 FSK-6 7 6
6 FSK-12 6 7 6
7 DE-16 FSK-12 8 12
8 FSK-16 12 8 12
9 DE-18 FSK-16 9 16
10 FSK-18 16 9 16
11 FSK-18 18
12 18 18

View File

@ -1,4 +1,7 @@
DA-A,1 F,0
DA-7,5 A,0
DA-11,6 7,7
DA-15,8 11,11
12,12
15,15
16,16

1 DA-A F 1 0
2 DA-7 A 5 0
3 DA-11 7 6 7
4 DA-15 11 8 11
5 12 12
6 15 15
7 16 16

View File

@ -1,6 +1,24 @@
ES-A,1 A,0
ES-APTA,1 A/fig,0
ES-7,3 A/i,0
ES-12,6 A/fig/i,0
ES-16,8 APTA,0
ES-18,11 TP,0
0+,0
6+,6
7/fig,7
7/i,7
7/i/fig,7
7,7
9+,9
10,10
12,12
12/fig,12
13,13
14,14
16,16
16/fig,16
18,18
18/fig,18
X,1000
Banned,1001

1 ES-A A 1 0
2 ES-APTA A/fig 1 0
3 ES-7 A/i 3 0
4 ES-12 A/fig/i 6 0
5 ES-16 APTA 8 0
6 ES-18 TP 11 0
7 0+ 0
8 6+ 6
9 7/fig 7
10 7/i 7
11 7/i/fig 7
12 7 7
13 9+ 9
14 10 10
15 12 12
16 12/fig 12
17 13 13
18 14 14
19 16 16
20 16/fig 16
21 18 18
22 18/fig 18
23 X 1000
24 Banned 1001

View File

@ -1,10 +1,10 @@
FI-S,1 S,0
FI-T,1 T,0
FI-7,4 K7,7
FI-12,5 7,7
FI-16,8 K12,12
FI-18,9 12,12
FI-K7,4 K16,16
FI-K12,5 16,16
FI-K16,8 K18,18
FI-K18,9 18,18

1 FI-S S 1 0
2 FI-T T 1 0
3 FI-7 K7 4 7
4 FI-12 7 5 7
5 FI-16 K12 8 12
6 FI-18 12 9 12
7 FI-K7 K16 4 16
8 FI-K12 16 5 16
9 FI-K16 K18 8 18
10 FI-K18 18 9 18

View File

@ -1,5 +1,12 @@
FR-U,1 Public Averti,0
FR-10,5 Tous Publics,0
FR-12,7 U,0
FR-16,9 0+,0
FR-18,10 6+,6
9+,9
10,10
12,12
14+,14
16,16
18,18
X,1000

1 FR-U Public Averti 1 0
2 FR-10 Tous Publics 5 0
3 FR-12 U 7 0
4 FR-16 0+ 9 0
5 FR-18 6+ 10 6
6 9+ 9
7 10 10
8 12 12
9 14+ 14
10 16 16
11 18 18
12 X 1000

View File

@ -1,7 +1,22 @@
GB-U,1 All,0
GB-PG,5 E,0
GB-12,6 G,0
GB-12A,7 U,0
GB-15,8 0+,0
GB-18,9 6+,6
GB-R18,15 7+,7
PG,8
9+,9
12,12
12+,12
12A,12
Teen,13
13+,13
14+,14
15,15
16,16
Caution,18
18,18
Mature,1000
Adult,1000
R18,1000

1 GB-U All 1 0
2 GB-PG E 5 0
3 GB-12 G 6 0
4 GB-12A U 7 0
5 GB-15 0+ 8 0
6 GB-18 6+ 9 6
7 GB-R18 7+ 15 7
8 PG 8
9 9+ 9
10 12 12
11 12+ 12
12 12A 12
13 Teen 13
14 13+ 13
15 14+ 14
16 15 15
17 16 16
18 Caution 18
19 18 18
20 Mature 1000
21 Adult 1000
22 R18 1000

View File

@ -1,6 +1,9 @@
IE-G,1 G,4
IE-PG,5 PG,12
IE-12A,7 12,12
IE-15A,8 12A,12
IE-16,9 12PG,12
IE-18,10 15,15
15A,15
16,16
18,18

1 IE-G G 1 4
2 IE-PG PG 5 12
3 IE-12A 12 7 12
4 IE-15A 12A 8 12
5 IE-16 12PG 9 12
6 IE-18 15 10 15
7 15A 15
8 16 16
9 18 18

View File

@ -1,4 +1,11 @@
JP-G,1 A,0
JP-PG12,7 G,0
JP-15+,8 B,12
JP-18+,10 PG12,12
C,15
15+,15
R15+,15
16+,16
D,17
Z,18
18+,18

1 JP-G A 1 0
2 JP-PG12 G 7 0
3 JP-15+ B 8 12
4 JP-18+ PG12 10 12
5 C 15
6 15+ 15
7 R15+ 15
8 16+ 16
9 D 17
10 Z 18
11 18+ 18

View File

@ -1,7 +1,6 @@
KZ-6-,0 K,0
KZ-6+,6 БА,12
KZ-12+,12 Б14,14
KZ-14+,14 E16,16
KZ-16+,16 E18,18
KZ-18+,18 HA,18
KZ-21+,21

1 KZ-6- K 0
2 KZ-6+ БА 6 12
3 KZ-12+ Б14 12 14
4 KZ-14+ E16 14 16
5 KZ-16+ E18 16 18
6 KZ-18+ HA 18
KZ-21+ 21

View File

@ -1,6 +1,6 @@
MX-AA,1 A,0
MX-A,5 AA,0
MX-B,7 B,12
MX-B-15,8 B-15,15
MX-C,9 C,18
MX-D,10 D,1000

1 MX-AA A 1 0
2 MX-A AA 5 0
3 MX-B B 7 12
4 MX-B-15 B-15 8 15
5 MX-C C 9 18
6 MX-D D 10 1000

View File

@ -1,6 +1,8 @@
NL-AL,1 AL,0
NL-MG6,2 MG6,6
NL-6,3 6,6
NL-9,5 9,9
NL-12,6 12,12
NL-16,8 14,14
16,16
18,18

1 NL-AL AL 1 0
2 NL-MG6 MG6 2 6
3 NL-6 6 3 6
4 NL-9 9 5 9
5 NL-12 12 6 12
6 NL-16 14 8 14
7 16 16
8 18 18

View File

@ -1,6 +1,9 @@
NO-A,1 A,0
NO-6,3 6,6
NO-9,4 7,7
NO-12,5 9,9
NO-15,8 11,11
NO-18,9 12,12
15,15
18,18
Not approved,1001

1 NO-A A 1 0
2 NO-6 6 3 6
3 NO-9 7 4 7
4 NO-12 9 5 9
5 NO-15 11 8 11
6 NO-18 12 9 12
7 15 15
8 18 18
9 Not approved 1001

View File

@ -1,11 +1,15 @@
NZ-G,1 Exempt,0
NZ-PG,5 G,0
NZ-M,6 GY,13
NZ-R13,7 PG,13
NZ-RP13,7 R13,13
NZ-R15,8 RP13,13
NZ-RP16,9 R15,15
NZ-R16,9 M,16
NZ-R18,10 R16,16
NZ-R,10 RP16,16
NZ-MA,10 GA,18
R18,18
MA,1000
R,1001
Objectionable,1001

1 NZ-G Exempt 1 0
2 NZ-PG G 5 0
3 NZ-M GY 6 13
4 NZ-R13 PG 7 13
5 NZ-RP13 R13 7 13
6 NZ-R15 RP13 8 13
7 NZ-RP16 R15 9 15
8 NZ-R16 M 9 16
9 NZ-R18 R16 10 16
10 NZ-R RP16 10 16
11 NZ-MA GA 10 18
12 R18 18
13 MA 1000
14 R 1001
15 Objectionable 1001

View File

@ -1 +1,6 @@
RO-AG,1 AG,0
AP-12,12
N-15,15
IM-18,18
IM-18-XXX,1000
IC,1001

1 RO-AG AG 1 0
2 AP-12 12
3 N-15 15
4 IM-18 18
5 IM-18-XXX 1000
6 IC 1001

View File

@ -1,5 +1,6 @@
RU-0+,1 0+,0
RU-6+,3 6+,6
RU-12+,7 12+,12
RU-16+,9 16+,16
RU-18+,10 18+,18
Refused classification,1001

1 RU-0+ 0+ 1 0
2 RU-6+ 6+ 3 6
3 RU-12+ 12+ 7 12
4 RU-16+ 16+ 9 16
5 RU-18+ 18+ 10 18
6 Refused classification 1001

View File

@ -1,5 +1,10 @@
SE-Btl,1 Alla,0
SE-Barntillåten,1 Barntillåten,0
SE-7,3 Btl,0
SE-11,5 0+,0
SE-15,8 7,7
9+,9
10+,10
11,11
14,14
15,15

1 SE-Btl Alla 1 0
2 SE-Barntillåten Barntillåten 1 0
3 SE-7 Btl 3 0
4 SE-11 0+ 5 0
5 SE-15 7 8 7
6 9+ 9
7 10+ 10
8 11 11
9 14 14
10 15 15

View File

@ -1,7 +1,22 @@
UK-U,1 All,0
UK-PG,5 E,0
UK-12,7 G,0
UK-12A,7 U,0
UK-15,9 0+,0
UK-18,10 6+,6
UK-R18,15 7+,7
PG,8
9+,9
12,12
12+,12
12A,12
Teen,13
13+,13
14+,14
15,15
16,16
Caution,18
18,18
Mature,1000
Adult,1000
R18,1000

1 UK-U All 1 0
2 UK-PG E 5 0
3 UK-12 G 7 0
4 UK-12A U 7 0
5 UK-15 0+ 9 0
6 UK-18 6+ 10 6
7 UK-R18 7+ 15 7
8 PG 8
9 9+ 9
10 12 12
11 12+ 12
12 12A 12
13 Teen 13
14 13+ 13
15 14+ 14
16 15 15
17 16 16
18 Caution 18
19 18 18
20 Mature 1000
21 Adult 1000
22 R18 1000

View File

@ -1,23 +1,50 @@
TV-Y,1 Approved,0
APPROVED,1 G,0
G,1 TV-G,0
E,1 TV-Y,0
EC,1 TV-Y7,7
TV-G,1 TV-Y7-FV,7
TV-Y7,3 PG,10
TV-Y7-FV,4 PG-13,13
PG,5 TV-PG,13
TV-PG,5 TV-PG-D,13
PG-13,7 TV-PG-L,13
T,7 TV-PG-S,13
TV-14,8 TV-PG-V,13
R,9 TV-PG-DL,13
M,9 TV-PG-DS,13
TV-MA,9 TV-PG-DV,13
NC-17,10 TV-PG-LS,13
AO,15 TV-PG-LV,13
RP,15 TV-PG-SV,13
UR,15 TV-PG-DLS,13
NR,15 TV-PG-DLV,13
X,15 TV-PG-DSV,13
XXX,100 TV-PG-LSV,13
TV-PG-DLSV,13
TV-14,14
TV-14-D,14
TV-14-L,14
TV-14-S,14
TV-14-V,14
TV-14-DL,14
TV-14-DS,14
TV-14-DV,14
TV-14-LS,14
TV-14-LV,14
TV-14-SV,14
TV-14-DLS,14
TV-14-DLV,14
TV-14-DSV,14
TV-14-LSV,14
TV-14-DLSV,14
NC-17,17
R,17
TV-MA,17
TV-MA-L,17
TV-MA-S,17
TV-MA-V,17
TV-MA-LS,17
TV-MA-LV,17
TV-MA-SV,17
TV-MA-LSV,17

1 TV-Y Approved 1 0
2 APPROVED G 1 0
3 G TV-G 1 0
4 E TV-Y 1 0
5 EC TV-Y7 1 7
6 TV-G TV-Y7-FV 1 7
7 TV-Y7 PG 3 10
8 TV-Y7-FV PG-13 4 13
9 PG TV-PG 5 13
10 TV-PG TV-PG-D 5 13
11 PG-13 TV-PG-L 7 13
12 T TV-PG-S 7 13
13 TV-14 TV-PG-V 8 13
14 R TV-PG-DL 9 13
15 M TV-PG-DS 9 13
16 TV-MA TV-PG-DV 9 13
17 NC-17 TV-PG-LS 10 13
18 AO TV-PG-LV 15 13
19 RP TV-PG-SV 15 13
20 UR TV-PG-DLS 15 13
21 NR TV-PG-DLV 15 13
22 X TV-PG-DSV 15 13
23 XXX TV-PG-LSV 100 13
24 TV-PG-DLSV 13
25 TV-14 14
26 TV-14-D 14
27 TV-14-L 14
28 TV-14-S 14
29 TV-14-V 14
30 TV-14-DL 14
31 TV-14-DS 14
32 TV-14-DV 14
33 TV-14-LS 14
34 TV-14-LV 14
35 TV-14-SV 14
36 TV-14-DLS 14
37 TV-14-DLV 14
38 TV-14-DSV 14
39 TV-14-LSV 14
40 TV-14-DLSV 14
41 NC-17 17
42 R 17
43 TV-MA 17
44 TV-MA-L 17
45 TV-MA-S 17
46 TV-MA-V 17
47 TV-MA-LS 17
48 TV-MA-LV 17
49 TV-MA-SV 17
50 TV-MA-LSV 17

View File

@ -93,11 +93,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger) public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger)
{ {
ArgumentNullException.ThrowIfNull(scheduledTask); ArgumentNullException.ThrowIfNull(scheduledTask);
ArgumentNullException.ThrowIfNull(applicationPaths); ArgumentNullException.ThrowIfNull(applicationPaths);
ArgumentNullException.ThrowIfNull(taskManager); ArgumentNullException.ThrowIfNull(taskManager);
ArgumentNullException.ThrowIfNull(logger); ArgumentNullException.ThrowIfNull(logger);
ScheduledTask = scheduledTask; ScheduledTask = scheduledTask;
@ -332,7 +329,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
return; return;
} }
_logger.LogInformation("{0} fired for task: {1}", trigger.GetType().Name, Name); _logger.LogDebug("{0} fired for task: {1}", trigger.GetType().Name, Name);
trigger.Stop(); trigger.Stop();
@ -378,7 +375,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
CurrentCancellationTokenSource = new CancellationTokenSource(); CurrentCancellationTokenSource = new CancellationTokenSource();
_logger.LogInformation("Executing {0}", Name); _logger.LogDebug("Executing {0}", Name);
((TaskManager)_taskManager).OnTaskExecuting(this); ((TaskManager)_taskManager).OnTaskExecuting(this);
@ -406,7 +403,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error"); _logger.LogError(ex, "Error executing Scheduled Task");
failureException = ex; failureException = ex;

View File

@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
{ {
var type = scheduledTask.ScheduledTask.GetType(); var type = scheduledTask.ScheduledTask.GetType();
_logger.LogInformation("Queuing task {0}", type.Name); _logger.LogDebug("Queuing task {0}", type.Name);
lock (_taskQueue) lock (_taskQueue)
{ {
@ -172,7 +172,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
{ {
var type = task.ScheduledTask.GetType(); var type = task.ScheduledTask.GetType();
_logger.LogInformation("Queuing task {0}", type.Name); _logger.LogDebug("Queuing task {0}", type.Name);
lock (_taskQueue) lock (_taskQueue)
{ {
@ -254,9 +254,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary> /// </summary>
private void ExecuteQueuedTasks() private void ExecuteQueuedTasks()
{ {
_logger.LogInformation("ExecuteQueuedTasks");
// Execute queued tasks
lock (_taskQueue) lock (_taskQueue)
{ {
var list = new List<Tuple<Type, TaskOptions>>(); var list = new List<Tuple<Type, TaskOptions>>();

View File

@ -46,6 +46,13 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
return Task.CompletedTask; return Task.CompletedTask;
} }
if (isApiKey)
{
// Api keys are unrestricted.
context.Succeed(requirement);
return Task.CompletedTask;
}
var isInLocalNetwork = _httpContextAccessor.HttpContext is not null var isInLocalNetwork = _httpContextAccessor.HttpContext is not null
&& _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIP()); && _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIP());
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
@ -62,7 +69,7 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
} }
// Admins can do everything // Admins can do everything
if (isApiKey || context.User.IsInRole(UserRoles.Administrator)) if (context.User.IsInRole(UserRoles.Administrator))
{ {
context.Succeed(requirement); context.Succeed(requirement);
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -172,12 +172,9 @@ public class GenresController : BaseJellyfinApiController
item ??= new Genre(); item ??= new Genre();
if (userId.Value.Equals(default)) var user = userId.Value.Equals(default)
{ ? null
return _dtoService.GetBaseItemDto(item, dtoOptions); : _userManager.GetUserById(userId.Value);
}
var user = _userManager.GetUserById(userId.Value);
return _dtoService.GetBaseItemDto(item, dtoOptions, user); return _dtoService.GetBaseItemDto(item, dtoOptions, user);
} }

View File

@ -98,7 +98,7 @@ public class ItemUpdateController : BaseJellyfinApiController
}).ToList()); }).ToList());
} }
UpdateItem(request, item); await UpdateItem(request, item).ConfigureAwait(false);
item.OnMetadataChanged(); item.OnMetadataChanged();
@ -147,7 +147,7 @@ public class ItemUpdateController : BaseJellyfinApiController
var info = new MetadataEditorInfo var info = new MetadataEditorInfo
{ {
ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(), ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(),
ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(), ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(),
Countries = _localizationManager.GetCountries().ToArray(), Countries = _localizationManager.GetCountries().ToArray(),
Cultures = _localizationManager.GetCultures().ToArray() Cultures = _localizationManager.GetCultures().ToArray()
@ -224,7 +224,7 @@ public class ItemUpdateController : BaseJellyfinApiController
return NoContent(); return NoContent();
} }
private void UpdateItem(BaseItemDto request, BaseItem item) private async Task UpdateItem(BaseItemDto request, BaseItem item)
{ {
item.Name = request.Name; item.Name = request.Name;
item.ForcedSortName = request.ForcedSortName; item.ForcedSortName = request.ForcedSortName;
@ -266,9 +266,50 @@ public class ItemUpdateController : BaseJellyfinApiController
item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : null; item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : null;
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : null; item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : null;
item.ProductionYear = request.ProductionYear; item.ProductionYear = request.ProductionYear;
item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
request.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
item.OfficialRating = request.OfficialRating;
item.CustomRating = request.CustomRating; item.CustomRating = request.CustomRating;
if (item is Series rseries)
{
foreach (Season season in rseries.Children)
{
season.OfficialRating = request.OfficialRating;
season.CustomRating = request.CustomRating;
season.OnMetadataChanged();
await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
foreach (Episode ep in season.Children)
{
ep.OfficialRating = request.OfficialRating;
ep.CustomRating = request.CustomRating;
ep.OnMetadataChanged();
await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
}
}
else if (item is Season season)
{
foreach (Episode ep in season.Children)
{
ep.OfficialRating = request.OfficialRating;
ep.CustomRating = request.CustomRating;
ep.OnMetadataChanged();
await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
}
else if (item is MusicAlbum album)
{
foreach (BaseItem track in album.Children)
{
track.OfficialRating = request.OfficialRating;
track.CustomRating = request.CustomRating;
track.OnMetadataChanged();
await track.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
}
if (request.ProductionLocations is not null) if (request.ProductionLocations is not null)
{ {
item.ProductionLocations = request.ProductionLocations; item.ProductionLocations = request.ProductionLocations;

View File

@ -411,6 +411,13 @@ public class ItemsController : BaseJellyfinApiController
query.SeriesStatuses = seriesStatus; query.SeriesStatuses = seriesStatus;
} }
// Exclude Blocked Unrated Items
var blockedUnratedItems = user?.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems);
if (blockedUnratedItems is not null)
{
query.BlockUnratedItems = blockedUnratedItems;
}
// ExcludeLocationTypes // ExcludeLocationTypes
if (excludeLocationTypes.Any(t => t == LocationType.Virtual)) if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
{ {

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -72,7 +73,7 @@ public class UserLibraryController : BaseJellyfinApiController
/// <param name="userId">User id.</param> /// <param name="userId">User id.</param>
/// <param name="itemId">Item id.</param> /// <param name="itemId">Item id.</param>
/// <response code="200">Item returned.</response> /// <response code="200">Item returned.</response>
/// <returns>An <see cref="OkResult"/> containing the d item.</returns> /// <returns>An <see cref="OkResult"/> containing the item.</returns>
[HttpGet("Users/{userId}/Items/{itemId}")] [HttpGet("Users/{userId}/Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
@ -86,11 +87,19 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.Equals(default) var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false); await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
@ -139,11 +148,19 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.Equals(default) var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false); var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray(); var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
@ -162,7 +179,29 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
return MarkFavorite(userId, itemId, true); var user = _userManager.GetUserById(userId);
if (user is null)
{
return NotFound();
}
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
if (item is null)
{
return NotFound();
}
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
return MarkFavorite(user, item, true);
} }
/// <summary> /// <summary>
@ -176,7 +215,29 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
return MarkFavorite(userId, itemId, false); var user = _userManager.GetUserById(userId);
if (user is null)
{
return NotFound();
}
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
if (item is null)
{
return NotFound();
}
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
return MarkFavorite(user, item, false);
} }
/// <summary> /// <summary>
@ -190,7 +251,29 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
return UpdateUserItemRatingInternal(userId, itemId, null); var user = _userManager.GetUserById(userId);
if (user is null)
{
return NotFound();
}
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
if (item is null)
{
return NotFound();
}
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
return UpdateUserItemRatingInternal(user, item, null);
} }
/// <summary> /// <summary>
@ -205,7 +288,29 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes) public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes)
{ {
return UpdateUserItemRatingInternal(userId, itemId, likes); var user = _userManager.GetUserById(userId);
if (user is null)
{
return NotFound();
}
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
if (item is null)
{
return NotFound();
}
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
return UpdateUserItemRatingInternal(user, item, likes);
} }
/// <summary> /// <summary>
@ -228,13 +333,20 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.Equals(default) var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
var dtoOptions = new DtoOptions().AddClientFields(User); if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
var dtoOptions = new DtoOptions().AddClientFields(User);
if (item is IHasTrailers hasTrailers) if (item is IHasTrailers hasTrailers)
{ {
var trailers = hasTrailers.LocalTrailers; var trailers = hasTrailers.LocalTrailers;
@ -266,11 +378,19 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.Equals(default) var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
return Ok(item return Ok(item
@ -385,15 +505,11 @@ public class UserLibraryController : BaseJellyfinApiController
/// <summary> /// <summary>
/// Marks the favorite. /// Marks the favorite.
/// </summary> /// </summary>
/// <param name="userId">The user id.</param> /// <param name="user">The user.</param>
/// <param name="itemId">The item id.</param> /// <param name="item">The item.</param>
/// <param name="isFavorite">if set to <c>true</c> [is favorite].</param> /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite) private UserItemDataDto MarkFavorite(User user, BaseItem item, bool isFavorite)
{ {
var user = _userManager.GetUserById(userId);
var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
// Get the user data for this item // Get the user data for this item
var data = _userDataRepository.GetUserData(user, item); var data = _userDataRepository.GetUserData(user, item);
@ -408,15 +524,11 @@ public class UserLibraryController : BaseJellyfinApiController
/// <summary> /// <summary>
/// Updates the user item rating. /// Updates the user item rating.
/// </summary> /// </summary>
/// <param name="userId">The user id.</param> /// <param name="user">The user.</param>
/// <param name="itemId">The item id.</param> /// <param name="item">The item.</param>
/// <param name="likes">if set to <c>true</c> [likes].</param> /// <param name="likes">if set to <c>true</c> [likes].</param>
private UserItemDataDto UpdateUserItemRatingInternal(Guid userId, Guid itemId, bool? likes) private UserItemDataDto UpdateUserItemRatingInternal(User user, BaseItem item, bool? likes)
{ {
var user = _userManager.GetUserById(userId);
var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
// Get the user data for this item // Get the user data for this item
var data = _userDataRepository.GetUserData(user, item); var data = _userDataRepository.GetUserData(user, item);
@ -455,6 +567,13 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
var result = await _lyricManager.GetLyrics(item).ConfigureAwait(false); var result = await _lyricManager.GetLyrics(item).ConfigureAwait(false);
if (result is not null) if (result is not null)
{ {

View File

@ -22,7 +22,8 @@ namespace Jellyfin.Server.Migrations
private static readonly Type[] _preStartupMigrationTypes = private static readonly Type[] _preStartupMigrationTypes =
{ {
typeof(PreStartupRoutines.CreateNetworkConfiguration), typeof(PreStartupRoutines.CreateNetworkConfiguration),
typeof(PreStartupRoutines.MigrateMusicBrainzTimeout) typeof(PreStartupRoutines.MigrateMusicBrainzTimeout),
typeof(PreStartupRoutines.MigrateRatingLevels)
}; };
/// <summary> /// <summary>

View File

@ -0,0 +1,86 @@
using System;
using System.Globalization;
using System.IO;
using Emby.Server.Implementations;
using MediaBrowser.Controller;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
namespace Jellyfin.Server.Migrations.PreStartupRoutines
{
/// <summary>
/// Migrate rating levels to new rating level system.
/// </summary>
internal class MigrateRatingLevels : IMigrationRoutine
{
private const string DbFilename = "library.db";
private readonly ILogger<MigrateRatingLevels> _logger;
private readonly IServerApplicationPaths _applicationPaths;
public MigrateRatingLevels(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory)
{
_applicationPaths = applicationPaths;
_logger = loggerFactory.CreateLogger<MigrateRatingLevels>();
}
/// <inheritdoc/>
public Guid Id => Guid.Parse("{67445D54-B895-4B24-9F4C-35CE0690EA07}");
/// <inheritdoc/>
public string Name => "MigrateRatingLevels";
/// <inheritdoc/>
public bool PerformOnNewInstall => false;
/// <inheritdoc/>
public void Perform()
{
var dataPath = _applicationPaths.DataPath;
var dbPath = Path.Combine(dataPath, DbFilename);
using (var connection = SQLite3.Open(
dbPath,
ConnectionFlags.ReadWrite,
null))
{
// Back up the database before deleting any entries
for (int i = 1; ; i++)
{
var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
if (!File.Exists(bakPath))
{
try
{
File.Copy(dbPath, bakPath);
_logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
throw;
}
}
}
// Migrate parental rating levels to new schema
_logger.LogInformation("Migrating parental rating levels.");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE OfficialRating = 'NR'");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE InheritedParentalRatingValue = ''");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE InheritedParentalRatingValue = 0");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 1000 WHERE InheritedParentalRatingValue = 100");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 1000 WHERE InheritedParentalRatingValue = 15");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 18 WHERE InheritedParentalRatingValue = 10");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 18 WHERE InheritedParentalRatingValue = 9");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 16 WHERE InheritedParentalRatingValue = 8");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 7");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 6");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 5");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 7 WHERE InheritedParentalRatingValue = 4");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 6 WHERE InheritedParentalRatingValue = 3");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 6 WHERE InheritedParentalRatingValue = 2");
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 0 WHERE InheritedParentalRatingValue = 1");
}
}
}
}

View File

@ -554,7 +554,7 @@ namespace MediaBrowser.Controller.Entities
public string OfficialRating { get; set; } public string OfficialRating { get; set; }
[JsonIgnore] [JsonIgnore]
public int InheritedParentalRatingValue { get; set; } public int? InheritedParentalRatingValue { get; set; }
/// <summary> /// <summary>
/// Gets or sets the critic rating. /// Gets or sets the critic rating.
@ -1534,12 +1534,6 @@ namespace MediaBrowser.Controller.Entities
} }
var maxAllowedRating = user.MaxParentalAgeRating; var maxAllowedRating = user.MaxParentalAgeRating;
if (maxAllowedRating is null)
{
return true;
}
var rating = CustomRatingForComparison; var rating = CustomRatingForComparison;
if (string.IsNullOrEmpty(rating)) if (string.IsNullOrEmpty(rating))
@ -1549,12 +1543,13 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrEmpty(rating)) if (string.IsNullOrEmpty(rating))
{ {
Logger.LogDebug("{0} has no parental rating set.", Name);
return !GetBlockUnratedValue(user); return !GetBlockUnratedValue(user);
} }
var value = LocalizationManager.GetRatingLevel(rating); var value = LocalizationManager.GetRatingLevel(rating);
// Could not determine the integer value // Could not determine rating level
if (!value.HasValue) if (!value.HasValue)
{ {
var isAllowed = !GetBlockUnratedValue(user); var isAllowed = !GetBlockUnratedValue(user);
@ -1567,7 +1562,7 @@ namespace MediaBrowser.Controller.Entities
return isAllowed; return isAllowed;
} }
return value.Value <= maxAllowedRating.Value; return !maxAllowedRating.HasValue || value.Value <= maxAllowedRating.Value;
} }
public int? GetInheritedParentalRatingValue() public int? GetInheritedParentalRatingValue()
@ -1627,10 +1622,10 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// Gets the block unrated value. /// Gets a bool indicating if access to the unrated item is blocked or not.
/// </summary> /// </summary>
/// <param name="user">The configuration.</param> /// <param name="user">The configuration.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if blocked, <c>false</c> otherwise.</returns>
protected virtual bool GetBlockUnratedValue(User user) protected virtual bool GetBlockUnratedValue(User user)
{ {
// Don't block plain folders that are unrated. Let the media underneath get blocked // Don't block plain folders that are unrated. Let the media underneath get blocked
@ -2517,7 +2512,7 @@ namespace MediaBrowser.Controller.Entities
var item = this; var item = this;
var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? 0; var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? null;
if (inheritedParentalRatingValue != item.InheritedParentalRatingValue) if (inheritedParentalRatingValue != item.InheritedParentalRatingValue)
{ {
item.InheritedParentalRatingValue = inheritedParentalRatingValue; item.InheritedParentalRatingValue = inheritedParentalRatingValue;

View File

@ -308,6 +308,11 @@ namespace MediaBrowser.Controller.Entities.TV
id.SeriesDisplayOrder = series.DisplayOrder; id.SeriesDisplayOrder = series.DisplayOrder;
} }
if (Season is not null)
{
id.SeasonProviderIds = Season.ProviderIds;
}
id.IsMissingEpisode = IsMissingEpisode; id.IsMissingEpisode = IsMissingEpisode;
id.IndexNumberEnd = IndexNumberEnd; id.IndexNumberEnd = IndexNumberEnd;

View File

@ -71,6 +71,21 @@ namespace MediaBrowser.Controller.MediaEncoding
"m4v", "m4v",
}; };
// Set max transcoding channels for encoders that can't handle more than a set amount of channels
// AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreCase)
{
{ "wmav2", 2 },
{ "libmp3lame", 2 },
{ "libfdk_aac", 6 },
{ "aac_at", 6 },
{ "ac3", 6 },
{ "eac3", 6 },
{ "dca", 6 },
{ "mlp", 6 },
{ "truehd", 6 },
};
public EncodingHelper( public EncodingHelper(
IApplicationPaths appPaths, IApplicationPaths appPaths,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
@ -2231,25 +2246,14 @@ namespace MediaBrowser.Controller.MediaEncoding
if (isTranscodingAudio) if (isTranscodingAudio)
{ {
// Set max transcoding channels for encoders that can't handle more than a set amount of channels var audioEncoder = GetAudioEncoder(state);
// AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
int transcoderChannelLimit = GetAudioEncoder(state) switch
{ {
string audioEncoder when audioEncoder.Equals("wmav2", StringComparison.OrdinalIgnoreCase) // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many channels.
|| audioEncoder.Equals("libmp3lame", StringComparison.OrdinalIgnoreCase) => 2, transcoderChannelLimit = 8;
string audioEncoder when audioEncoder.Equals("libfdk_aac", StringComparison.OrdinalIgnoreCase) }
|| audioEncoder.Equals("aac_at", StringComparison.OrdinalIgnoreCase)
|| audioEncoder.Equals("ac3", StringComparison.OrdinalIgnoreCase)
|| audioEncoder.Equals("eac3", StringComparison.OrdinalIgnoreCase)
|| audioEncoder.Equals("dts", StringComparison.OrdinalIgnoreCase)
|| audioEncoder.Equals("mlp", StringComparison.OrdinalIgnoreCase)
|| audioEncoder.Equals("truehd", StringComparison.OrdinalIgnoreCase) => 6,
// Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many channels
_ => 8,
};
// Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelLimit // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelLimit
resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? transcoderChannelLimit; resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? transcoderChannelLimit;
if (request.TranscodingMaxAudioChannels < resultChannels) if (request.TranscodingMaxAudioChannels < resultChannels)
@ -4228,12 +4232,12 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter); subFilters.Add(subTextSubtitlesFilter);
} }
subFilters.Add("hwupload=derive_device=vulkan:extra_hw_frames=16"); // prefer vaapi hwupload to vulkan hwupload,
// Mesa RADV does not support a dedicated transfer queue.
subFilters.Add("hwupload=derive_device=vaapi,format=vaapi,hwmap=derive_device=vulkan");
overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
overlayFilters.Add("scale_vulkan=format=nv12");
// explicitly sync using libplacebo.
overlayFilters.Add("libplacebo=format=nv12:upscaler=none:downscaler=none");
// OUTPUT vaapi(nv12/bgra) surface(vram) // OUTPUT vaapi(nv12/bgra) surface(vram)
// reverse-mapping via vaapi-vulkan interop. // reverse-mapping via vaapi-vulkan interop.

View File

@ -232,6 +232,11 @@ namespace MediaBrowser.Controller.Net
// TODO Investigate and properly fix. // TODO Investigate and properly fix.
Logger.LogError(ex, "Object Disposed"); Logger.LogError(ex, "Object Disposed");
} }
catch (Exception ex)
{
// TODO Investigate and properly fix.
Logger.LogError(ex, "Error disposing websocket");
}
lock (_activeConnections) lock (_activeConnections)
{ {

View File

@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Persistence
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken); void SaveItems(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken);
void SaveImages(BaseItem item); void SaveImages(BaseItem item);

View File

@ -12,10 +12,13 @@ namespace MediaBrowser.Controller.Providers
public EpisodeInfo() public EpisodeInfo()
{ {
SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
SeasonProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }
public Dictionary<string, string> SeriesProviderIds { get; set; } public Dictionary<string, string> SeriesProviderIds { get; set; }
public Dictionary<string, string> SeasonProviderIds { get; set; }
public int? IndexNumberEnd { get; set; } public int? IndexNumberEnd { get; set; }
public bool IsMissingEpisode { get; set; } public bool IsMissingEpisode { get; set; }

View File

@ -26,6 +26,7 @@ namespace MediaBrowser.Controller.Providers
ReplaceAllMetadata = copy.ReplaceAllMetadata; ReplaceAllMetadata = copy.ReplaceAllMetadata;
EnableRemoteContentProbe = copy.EnableRemoteContentProbe; EnableRemoteContentProbe = copy.EnableRemoteContentProbe;
IsAutomated = copy.IsAutomated;
ImageRefreshMode = copy.ImageRefreshMode; ImageRefreshMode = copy.ImageRefreshMode;
ReplaceAllImages = copy.ReplaceAllImages; ReplaceAllImages = copy.ReplaceAllImages;
ReplaceImages = copy.ReplaceImages; ReplaceImages = copy.ReplaceImages;

View File

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

View File

@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Entities
{ {
} }
public ParentalRating(string name, int value) public ParentalRating(string name, int? value)
{ {
Name = name; Name = name;
Value = value; Value = value;
@ -28,6 +28,6 @@ namespace MediaBrowser.Model.Entities
/// Gets or sets the value. /// Gets or sets the value.
/// </summary> /// </summary>
/// <value>The value.</value> /// <value>The value.</value>
public int Value { get; set; } public int? Value { get; set; }
} }
} }

View File

@ -46,6 +46,7 @@ namespace MediaBrowser.Model.Users
LoginAttemptsBeforeLockout = -1; LoginAttemptsBeforeLockout = -1;
MaxActiveSessions = 0; MaxActiveSessions = 0;
MaxParentalRating = null;
EnableAllChannels = true; EnableAllChannels = true;
EnabledChannels = Array.Empty<Guid>(); EnabledChannels = Array.Empty<Guid>();

View File

@ -284,12 +284,12 @@ namespace MediaBrowser.Providers.Manager
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
return new List<RemoteImageInfo>(); return Enumerable.Empty<RemoteImageInfo>();
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path); _logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
return new List<RemoteImageInfo>(); return Enumerable.Empty<RemoteImageInfo>();
} }
} }

View File

@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Playlists
return GetPlsItems(stream); return GetPlsItems(stream);
} }
return new List<LinkedChild>(); return Enumerable.Empty<LinkedChild>();
} }
private IEnumerable<LinkedChild> GetPlsItems(Stream stream) private IEnumerable<LinkedChild> GetPlsItems(Stream stream)

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
@ -42,7 +43,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return new List<ImageType> return new ImageType[]
{ {
ImageType.Primary, ImageType.Primary,
ImageType.Logo, ImageType.Logo,
@ -74,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
} }
} }
return new List<RemoteImageInfo>(); return Enumerable.Empty<RemoteImageInfo>();
} }
private IEnumerable<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item) private IEnumerable<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item)

View File

@ -1,12 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>AudioDB</title> <title>TheAudioDB</title>
</head> </head>
<body> <body>
<div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
<div data-role="content"> <div data-role="content">
<div class="content-primary"> <div class="content-primary">
<h1>TheAudioDB</h1>
<form class="configForm"> <form class="configForm">
<label class="checkboxContainer"> <label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="replaceAlbumName" /> <input is="emby-checkbox" type="checkbox" id="replaceAlbumName" />

View File

@ -7,16 +7,22 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
/// </summary> /// </summary>
public class PluginConfiguration : BasePluginConfiguration public class PluginConfiguration : BasePluginConfiguration
{ {
private const string DefaultServer = "https://musicbrainz.org"; /// <summary>
/// The default server URL.
/// </summary>
public const string DefaultServer = "https://musicbrainz.org";
private const double DefaultRateLimit = 1.0; /// <summary>
/// The default rate limit.
/// </summary>
public const double DefaultRateLimit = 1.0;
private string _server = DefaultServer; private string _server = DefaultServer;
private double _rateLimit = DefaultRateLimit; private double _rateLimit = DefaultRateLimit;
/// <summary> /// <summary>
/// Gets or sets the server url. /// Gets or sets the server URL.
/// </summary> /// </summary>
public string Server public string Server
{ {

View File

@ -1,9 +1,14 @@
<div id="musicBrainzConfigurationPage" data-role="page" <!DOCTYPE html>
class="page type-interior pluginConfigurationPage musicBrainzConfigurationPage" data-require="emby-input,emby-button,emby-checkbox"> <html>
<head>
<title>MusicBrainz</title>
</head>
<body>
<div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
<div data-role="content"> <div data-role="content">
<div class="content-primary"> <div class="content-primary">
<h1>MusicBrainz</h1> <h1>MusicBrainz</h1>
<form class="musicBrainzConfigurationForm"> <form class="configForm">
<div class="inputContainer"> <div class="inputContainer">
<input is="emby-input" type="text" id="server" required label="Server" /> <input is="emby-input" type="text" id="server" required label="Server" />
<div class="fieldDescription">This can be a mirror of the official server or even a custom server.</div> <div class="fieldDescription">This can be a mirror of the official server or even a custom server.</div>
@ -28,7 +33,7 @@
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a" uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
}; };
document.querySelector('.musicBrainzConfigurationPage') document.querySelector('.configPage')
.addEventListener('pageshow', function () { .addEventListener('pageshow', function () {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) { ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
@ -52,7 +57,7 @@
}); });
}); });
document.querySelector('.musicBrainzConfigurationForm') document.querySelector('.configForm')
.addEventListener('submit', function (e) { .addEventListener('submit', function (e) {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();

View File

@ -58,7 +58,7 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
{ {
// Fallback to official server // Fallback to official server
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server"); _logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
var defaultServer = new Uri(configuration.Server); var defaultServer = new Uri(PluginConfiguration.DefaultServer);
Query.DefaultServer = defaultServer.Host; Query.DefaultServer = defaultServer.Host;
Query.DefaultPort = defaultServer.Port; Query.DefaultPort = defaultServer.Port;
Query.DefaultUrlScheme = defaultServer.Scheme; Query.DefaultUrlScheme = defaultServer.Scheme;
@ -157,10 +157,10 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
var artists = releaseSearchResult.ArtistCredit; var artists = releaseSearchResult.ArtistCredit;
if (artists is not null && artists.Count > 0) if (artists is not null && artists.Count > 0)
{ {
var artistResults = new List<RemoteSearchResult>(); var artistResults = new RemoteSearchResult[artists.Count];
for (int i = 0; i < artists.Count; i++)
foreach (var artist in artists)
{ {
var artist = artists[i];
var artistResult = new RemoteSearchResult var artistResult = new RemoteSearchResult
{ {
Name = artist.Name Name = artist.Name
@ -171,11 +171,11 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
artistResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Artist!.Id.ToString()); artistResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Artist!.Id.ToString());
} }
artistResults.Add(artistResult); artistResults[i] = artistResult;
} }
searchResult.AlbumArtist = artistResults[0]; searchResult.AlbumArtist = artistResults[0];
searchResult.Artists = artistResults.ToArray(); searchResult.Artists = artistResults;
} }
searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString()); searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());

View File

@ -55,7 +55,7 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar
{ {
// Fallback to official server // Fallback to official server
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server"); _logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
var defaultServer = new Uri(configuration.Server); var defaultServer = new Uri(PluginConfiguration.DefaultServer);
Query.DefaultServer = defaultServer.Host; Query.DefaultServer = defaultServer.Host;
Query.DefaultPort = defaultServer.Port; Query.DefaultPort = defaultServer.Port;
Query.DefaultUrlScheme = defaultServer.Scheme; Query.DefaultUrlScheme = defaultServer.Scheme;

View File

@ -4,9 +4,10 @@
<title>OMDb</title> <title>OMDb</title>
</head> </head>
<body> <body>
<div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
<div data-role="content"> <div data-role="content">
<div class="content-primary"> <div class="content-primary">
<h1>OMDb</h1>
<form class="configForm"> <form class="configForm">
<label class="checkboxContainer"> <label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="castAndCrew" /> <input is="emby-checkbox" type="checkbox" id="castAndCrew" />

View File

@ -38,10 +38,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return new List<ImageType> yield return ImageType.Primary;
{
ImageType.Primary
};
} }
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)

View File

@ -4,9 +4,10 @@
<title>Studio Images</title> <title>Studio Images</title>
</head> </head>
<body> <body>
<div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
<div data-role="content"> <div data-role="content">
<div class="content-primary"> <div class="content-primary">
<h1>Studio Images</h1>
<form class="configForm"> <form class="configForm">
<div class="inputContainer"> <div class="inputContainer">
<input is="emby-input" type="text" id="repository" label="Repository" /> <input is="emby-input" type="text" id="repository" label="Repository" />

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -50,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return new List<ImageType> return new ImageType[]
{ {
ImageType.Primary, ImageType.Primary,
ImageType.Backdrop ImageType.Backdrop

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -74,7 +72,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false); var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
var collections = new List<RemoteSearchResult>(); var collections = new RemoteSearchResult[collectionSearchResults.Count];
for (var i = 0; i < collectionSearchResults.Count; i++) for (var i = 0; i < collectionSearchResults.Count; i++)
{ {
var collection = new RemoteSearchResult var collection = new RemoteSearchResult
@ -84,7 +82,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
}; };
collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture)); collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
collections.Add(collection); collections[i] = collection;
} }
return collections; return collections;

View File

@ -4,9 +4,10 @@
<title>TMDb</title> <title>TMDb</title>
</head> </head>
<body> <body>
<div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
<div data-role="content"> <div data-role="content">
<div class="content-primary"> <div class="content-primary">
<h1>TMDb</h1>
<form class="configForm"> <form class="configForm">
<label class="checkboxContainer"> <label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="includeAdult" /> <input is="emby-checkbox" type="checkbox" id="includeAdult" />

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -51,7 +49,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return new List<ImageType> return new ImageType[]
{ {
ImageType.Primary, ImageType.Primary,
ImageType.Backdrop, ImageType.Backdrop,

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -64,6 +62,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
if (movie is not null)
{
var remoteResult = new RemoteSearchResult var remoteResult = new RemoteSearchResult
{ {
Name = movie.Title ?? movie.OriginalTitle, Name = movie.Title ?? movie.OriginalTitle,
@ -88,8 +88,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return new[] { remoteResult }; return new[] { remoteResult };
} }
}
IReadOnlyList<SearchMovie> movieResults; IReadOnlyList<SearchMovie>? movieResults = null;
if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id)) if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id))
{ {
var result = await _tmdbClientManager.FindByExternalIdAsync( var result = await _tmdbClientManager.FindByExternalIdAsync(
@ -97,18 +98,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
FindExternalSource.Imdb, FindExternalSource.Imdb,
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
movieResults = result.MovieResults; movieResults = result?.MovieResults;
} }
else if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
if (movieResults is null && searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
{ {
var result = await _tmdbClientManager.FindByExternalIdAsync( var result = await _tmdbClientManager.FindByExternalIdAsync(
id, id,
FindExternalSource.TvDb, FindExternalSource.TvDb,
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
movieResults = result.MovieResults; movieResults = result?.MovieResults;
} }
else
if (movieResults is null)
{ {
movieResults = await _tmdbClientManager movieResults = await _tmdbClientManager
.SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken) .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken)

View File

@ -46,10 +46,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return new List<ImageType> yield return ImageType.Primary;
{
ImageType.Primary
};
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -69,7 +67,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false); var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
var remoteSearchResults = new List<RemoteSearchResult>(); var remoteSearchResults = new RemoteSearchResult[personSearchResult.Count];
for (var i = 0; i < personSearchResult.Count; i++) for (var i = 0; i < personSearchResult.Count; i++)
{ {
var person = personSearchResult[i]; var person = personSearchResult[i];
@ -81,7 +79,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
}; };
remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture)); remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
remoteSearchResults.Add(remoteSearchResult); remoteSearchResults[i] = remoteSearchResult;
} }
return remoteSearchResults; return remoteSearchResults;
@ -107,6 +105,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
if (personTmdbId > 0) if (personTmdbId > 0)
{ {
var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
if (person is null)
{
return result;
}
result.HasMetadata = true; result.HasMetadata = true;

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -49,10 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return new List<ImageType> yield return ImageType.Primary;
{
ImageType.Primary
};
} }
/// <inheritdoc /> /// <inheritdoc />
@ -63,7 +58,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
if (seriesTmdbId <= 0) if (series is null || seriesTmdbId <= 0)
{ {
return Enumerable.Empty<RemoteImageInfo>(); return Enumerable.Empty<RemoteImageInfo>();
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -87,7 +85,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return metadataResult; return metadataResult;
} }
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string tmdbId); info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? tmdbId);
var seriesTmdbId = Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture); var seriesTmdbId = Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture);
if (seriesTmdbId <= 0) if (seriesTmdbId <= 0)

View File

@ -48,10 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return new List<ImageType> yield return ImageType.Primary;
{
ImageType.Primary
};
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{ {
return new List<ImageType> return new ImageType[]
{ {
ImageType.Primary, ImageType.Primary,
ImageType.Backdrop, ImageType.Backdrop,

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -211,7 +209,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
} }
} }
if (string.IsNullOrEmpty(tmdbId)) if (!int.TryParse(tmdbId, CultureInfo.InvariantCulture, out int tmdbIdInt))
{ {
return result; return result;
} }
@ -219,9 +217,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var tvShow = await _tmdbClientManager var tvShow = await _tmdbClientManager
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .GetSeriesAsync(tmdbIdInt, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
if (tvShow is null)
{
return result;
}
result = new MetadataResult<Series> result = new MetadataResult<Series>
{ {
Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode), Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),

View File

@ -1,6 +1,4 @@
#nullable disable using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Threading; using System.Threading;
@ -50,10 +48,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="imageLanguages">A comma-separated list of image languages.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb movie or null if not found.</returns> /// <returns>The TMDb movie or null if not found.</returns>
public async Task<Movie> GetMovieAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken) public async Task<Movie?> GetMovieAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
{ {
var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out Movie movie)) if (_memoryCache.TryGetValue(key, out Movie? movie))
{ {
return movie; return movie;
} }
@ -89,10 +87,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="imageLanguages">A comma-separated list of image languages.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb collection or null if not found.</returns> /// <returns>The TMDb collection or null if not found.</returns>
public async Task<Collection> GetCollectionAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken) public async Task<Collection?> GetCollectionAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
{ {
var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out Collection collection)) if (_memoryCache.TryGetValue(key, out Collection? collection))
{ {
return collection; return collection;
} }
@ -122,10 +120,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="imageLanguages">A comma-separated list of image languages.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv show information or null if not found.</returns> /// <returns>The TMDb tv show information or null if not found.</returns>
public async Task<TvShow> GetSeriesAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken) public async Task<TvShow?> GetSeriesAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
{ {
var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out TvShow series)) if (_memoryCache.TryGetValue(key, out TvShow? series))
{ {
return series; return series;
} }
@ -162,7 +160,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="imageLanguages">A comma-separated list of image languages.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv show episode group information or null if not found.</returns> /// <returns>The TMDb tv show episode group information or null if not found.</returns>
private async Task<TvGroupCollection> GetSeriesGroupAsync(int tvShowId, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) private async Task<TvGroupCollection?> GetSeriesGroupAsync(int tvShowId, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken)
{ {
TvGroupType? groupType = TvGroupType? groupType =
string.Equals(displayOrder, "originalAirDate", StringComparison.Ordinal) ? TvGroupType.OriginalAirDate : string.Equals(displayOrder, "originalAirDate", StringComparison.Ordinal) ? TvGroupType.OriginalAirDate :
@ -180,7 +178,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
} }
var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
if (_memoryCache.TryGetValue(key, out TvGroupCollection group)) if (_memoryCache.TryGetValue(key, out TvGroupCollection? group))
{ {
return group; return group;
} }
@ -217,10 +215,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="imageLanguages">A comma-separated list of image languages.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv season information or null if not found.</returns> /// <returns>The TMDb tv season information or null if not found.</returns>
public async Task<TvSeason> GetSeasonAsync(int tvShowId, int seasonNumber, string language, string imageLanguages, CancellationToken cancellationToken) public async Task<TvSeason?> GetSeasonAsync(int tvShowId, int seasonNumber, string? language, string? imageLanguages, CancellationToken cancellationToken)
{ {
var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}"; var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out TvSeason season)) if (_memoryCache.TryGetValue(key, out TvSeason? season))
{ {
return season; return season;
} }
@ -254,10 +252,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="imageLanguages">A comma-separated list of image languages.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv episode information or null if not found.</returns> /// <returns>The TMDb tv episode information or null if not found.</returns>
public async Task<TvEpisode> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken)
{ {
var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
if (_memoryCache.TryGetValue(key, out TvEpisode episode)) if (_memoryCache.TryGetValue(key, out TvEpisode? episode))
{ {
return episode; return episode;
} }
@ -301,10 +299,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="language">The episode's language.</param> /// <param name="language">The episode's language.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb person information or null if not found.</returns> /// <returns>The TMDb person information or null if not found.</returns>
public async Task<Person> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken) public async Task<Person?> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken)
{ {
var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out Person person)) if (_memoryCache.TryGetValue(key, out Person? person))
{ {
return person; return person;
} }
@ -333,14 +331,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="language">The item's language.</param> /// <param name="language">The item's language.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb item or null if not found.</returns> /// <returns>The TMDb item or null if not found.</returns>
public async Task<FindContainer> FindByExternalIdAsync( public async Task<FindContainer?> FindByExternalIdAsync(
string externalId, string externalId,
FindExternalSource source, FindExternalSource source,
string language, string language,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}"; var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out FindContainer result)) if (_memoryCache.TryGetValue(key, out FindContainer? result))
{ {
return result; return result;
} }
@ -372,7 +370,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default) public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default)
{ {
var key = $"searchseries-{name}-{language}"; var key = $"searchseries-{name}-{language}";
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv> series)) if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv>? series) && series is not null)
{ {
return series.Results; return series.Results;
} }
@ -400,7 +398,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public async Task<IReadOnlyList<SearchPerson>> SearchPersonAsync(string name, CancellationToken cancellationToken) public async Task<IReadOnlyList<SearchPerson>> SearchPersonAsync(string name, CancellationToken cancellationToken)
{ {
var key = $"searchperson-{name}"; var key = $"searchperson-{name}";
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson> person)) if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson>? person) && person is not null)
{ {
return person.Results; return person.Results;
} }
@ -442,7 +440,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, CancellationToken cancellationToken) public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, CancellationToken cancellationToken)
{ {
var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}"; var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie> movies)) if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie>? movies) && movies is not null)
{ {
return movies.Results; return movies.Results;
} }
@ -471,7 +469,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken) public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken)
{ {
var key = $"collectionsearch-{name}-{language}"; var key = $"collectionsearch-{name}-{language}";
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection> collections)) if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection>? collections) && collections is not null)
{ {
return collections.Results; return collections.Results;
} }
@ -496,7 +494,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="size">The image size to fetch.</param> /// <param name="size">The image size to fetch.</param>
/// <param name="path">The relative URL of the image.</param> /// <param name="path">The relative URL of the image.</param>
/// <returns>The absolute URL.</returns> /// <returns>The absolute URL.</returns>
private string GetUrl(string size, string path) private string? GetUrl(string? size, string path)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
@ -511,7 +509,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary> /// </summary>
/// <param name="posterPath">The relative URL of the poster.</param> /// <param name="posterPath">The relative URL of the poster.</param>
/// <returns>The absolute URL.</returns> /// <returns>The absolute URL.</returns>
public string GetPosterUrl(string posterPath) public string? GetPosterUrl(string posterPath)
{ {
return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath); return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath);
} }
@ -521,7 +519,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary> /// </summary>
/// <param name="actorProfilePath">The relative URL of the profile image.</param> /// <param name="actorProfilePath">The relative URL of the profile image.</param>
/// <returns>The absolute URL.</returns> /// <returns>The absolute URL.</returns>
public string GetProfileUrl(string actorProfilePath) public string? GetProfileUrl(string actorProfilePath)
{ {
return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath); return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath);
} }
@ -579,7 +577,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="type">The type of the image.</param> /// <param name="type">The type of the image.</param>
/// <param name="requestLanguage">The requested language.</param> /// <param name="requestLanguage">The requested language.</param>
/// <returns>The remote images.</returns> /// <returns>The remote images.</returns>
private IEnumerable<RemoteImageInfo> ConvertToRemoteImageInfo(IReadOnlyList<ImageData> images, string size, ImageType type, string requestLanguage) private IEnumerable<RemoteImageInfo> ConvertToRemoteImageInfo(IReadOnlyList<ImageData> images, string? size, ImageType type, string requestLanguage)
{ {
// sizes provided are for original resolution, don't store them when downloading scaled images // sizes provided are for original resolution, don't store them when downloading scaled images
var scaleImage = !string.Equals(size, "original", StringComparison.OrdinalIgnoreCase); var scaleImage = !string.Equals(size, "original", StringComparison.OrdinalIgnoreCase);

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using TMDbLib.Objects.General; using TMDbLib.Objects.General;
@ -128,7 +129,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary> /// </summary>
/// <param name="language">The language code.</param> /// <param name="language">The language code.</param>
/// <returns>The normalized language code.</returns> /// <returns>The normalized language code.</returns>
public static string NormalizeLanguage(string language) [return: NotNullIfNotNull(nameof(language))]
public static string? NormalizeLanguage(string? language)
{ {
if (string.IsNullOrEmpty(language)) if (string.IsNullOrEmpty(language))
{ {

View File

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -56,7 +54,7 @@ namespace MediaBrowser.Providers.Subtitles
} }
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<SubtitleDownloadFailureEventArgs> SubtitleDownloadFailure; public event EventHandler<SubtitleDownloadFailureEventArgs>? SubtitleDownloadFailure;
/// <inheritdoc /> /// <inheritdoc />
public async Task<RemoteSubtitleInfo[]> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken) public async Task<RemoteSubtitleInfo[]> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
@ -235,7 +233,7 @@ namespace MediaBrowser.Providers.Subtitles
private async Task TrySaveToFiles(Stream stream, List<string> savePaths) private async Task TrySaveToFiles(Stream stream, List<string> savePaths)
{ {
List<Exception> exs = null; List<Exception>? exs = null;
foreach (var savePath in savePaths) foreach (var savePath in savePaths)
{ {
@ -245,7 +243,7 @@ namespace MediaBrowser.Providers.Subtitles
try try
{ {
Directory.CreateDirectory(Path.GetDirectoryName(savePath)); Directory.CreateDirectory(Path.GetDirectoryName(savePath) ?? throw new InvalidOperationException("Path can't be a root directory."));
var fileOptions = AsyncFile.WriteOptions; var fileOptions = AsyncFile.WriteOptions;
fileOptions.Mode = FileMode.CreateNew; fileOptions.Mode = FileMode.CreateNew;

View File

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -12,7 +12,7 @@ namespace Jellyfin.Extensions
{ {
// Matches non-conforming unicode chars // Matches non-conforming unicode chars
// https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)"); private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)", RegexOptions.Compiled);
/// <summary> /// <summary>
/// Removes the diacritics character from the strings. /// Removes the diacritics character from the strings.

View File

@ -1,9 +1,13 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using AutoFixture; using AutoFixture;
using AutoFixture.AutoMoq; using AutoFixture.AutoMoq;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Data.Entities;
using Jellyfin.Server.Implementations.Security; using Jellyfin.Server.Implementations.Security;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -51,6 +55,32 @@ namespace Jellyfin.Api.Tests.Auth.DefaultAuthorizationPolicy
Assert.True(context.HasSucceeded); Assert.True(context.HasSucceeded);
} }
[Fact]
public async Task ShouldSucceedOnApiKey()
{
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
_httpContextAccessor
.Setup(h => h.HttpContext!.Connection.RemoteIpAddress)
.Returns(new IPAddress(0));
_userManagerMock
.Setup(u => u.GetUserById(It.IsAny<Guid>()))
.Returns<User>(null);
var claims = new[]
{
new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
};
var identity = new ClaimsIdentity(claims, string.Empty);
var principal = new ClaimsPrincipal(identity);
var context = new AuthorizationHandlerContext(_requirements, principal, null);
await _sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory] [Theory]
[MemberData(nameof(GetParts_ValidAuthHeader_Success_Data))] [MemberData(nameof(GetParts_ValidAuthHeader_Success_Data))]
public void GetParts_ValidAuthHeader_Success(string input, Dictionary<string, string> parts) public void GetParts_ValidAuthHeader_Success(string input, Dictionary<string, string> parts)

View File

@ -0,0 +1,47 @@
using Emby.Dlna.Server;
using MediaBrowser.Model.Dlna;
using Xunit;
namespace Jellyfin.Dlna.Server.Tests;
public class DescriptionXmlBuilderTests
{
[Fact]
public void GetFriendlyName_EmptyProfile_ReturnsServerName()
{
const string ServerName = "Test Server Name";
var builder = new DescriptionXmlBuilder(new DeviceProfile(), "serverUdn", "localhost", ServerName, string.Empty);
Assert.Equal(ServerName, builder.GetFriendlyName());
}
[Fact]
public void GetFriendlyName_FriendlyName_ReturnsFriendlyName()
{
const string FriendlyName = "Friendly Neighborhood Test Server";
var builder = new DescriptionXmlBuilder(
new DeviceProfile()
{
FriendlyName = FriendlyName
},
"serverUdn",
"localhost",
"Test Server Name",
string.Empty);
Assert.Equal(FriendlyName, builder.GetFriendlyName());
}
[Fact]
public void GetFriendlyName_FriendlyNameInterpolation_ReturnsFriendlyName()
{
var builder = new DescriptionXmlBuilder(
new DeviceProfile()
{
FriendlyName = "Friendly Neighborhood ${HostName}"
},
"serverUdn",
"localhost",
"Test Server Name",
string.Empty);
Assert.Equal("Friendly Neighborhood TestServerName", builder.GetFriendlyName());
}
}

Some files were not shown because too many files have changed in this diff Show More