Merge pull request #5938 from cvium/allocationz
This commit is contained in:
commit
48e81e65e8
|
@ -1002,15 +1002,12 @@ namespace Emby.Server.Implementations.Data
|
|||
return;
|
||||
}
|
||||
|
||||
var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var part in parts)
|
||||
foreach (var part in value.SpanSplit('|'))
|
||||
{
|
||||
var idParts = part.Split('=');
|
||||
|
||||
if (idParts.Length == 2)
|
||||
var providerDelimiterIndex = part.IndexOf('=');
|
||||
if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('='))
|
||||
{
|
||||
item.SetProviderId(idParts[0], idParts[1]);
|
||||
item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1045,9 +1042,8 @@ namespace Emby.Server.Implementations.Data
|
|||
return Array.Empty<ItemImageInfo>();
|
||||
}
|
||||
|
||||
var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries);
|
||||
var list = new List<ItemImageInfo>();
|
||||
foreach (var part in parts)
|
||||
foreach (var part in value.SpanSplit('|'))
|
||||
{
|
||||
var image = ItemImageInfoFromValueString(part);
|
||||
|
||||
|
@ -1086,41 +1082,93 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
}
|
||||
|
||||
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
||||
internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value)
|
||||
{
|
||||
var parts = value.Split('*', StringSplitOptions.None);
|
||||
|
||||
if (parts.Length < 3)
|
||||
var nextSegment = value.IndexOf('*');
|
||||
if (nextSegment == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var image = new ItemImageInfo();
|
||||
ReadOnlySpan<char> path = value[..nextSegment];
|
||||
value = value[(nextSegment + 1)..];
|
||||
nextSegment = value.IndexOf('*');
|
||||
if (nextSegment == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
image.Path = RestorePath(parts[0]);
|
||||
ReadOnlySpan<char> dateModified = value[..nextSegment];
|
||||
value = value[(nextSegment + 1)..];
|
||||
nextSegment = value.IndexOf('*');
|
||||
if (nextSegment == -1)
|
||||
{
|
||||
nextSegment = value.Length;
|
||||
}
|
||||
|
||||
if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
|
||||
ReadOnlySpan<char> imageType = value[..nextSegment];
|
||||
|
||||
var image = new ItemImageInfo
|
||||
{
|
||||
Path = RestorePath(path.ToString())
|
||||
};
|
||||
|
||||
if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
|
||||
{
|
||||
image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
if (Enum.TryParse(parts[2], true, out ImageType type))
|
||||
if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
|
||||
{
|
||||
image.Type = type;
|
||||
}
|
||||
|
||||
if (parts.Length >= 5)
|
||||
// Optional parameters: width*height*blurhash
|
||||
if (nextSegment + 1 < value.Length - 1)
|
||||
{
|
||||
if (int.TryParse(parts[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
|
||||
&& int.TryParse(parts[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
|
||||
value = value[(nextSegment + 1)..];
|
||||
nextSegment = value.IndexOf('*');
|
||||
if (nextSegment == -1 || nextSegment == value.Length)
|
||||
{
|
||||
return image;
|
||||
}
|
||||
|
||||
ReadOnlySpan<char> widthSpan = value[..nextSegment];
|
||||
|
||||
value = value[(nextSegment + 1)..];
|
||||
nextSegment = value.IndexOf('*');
|
||||
if (nextSegment == -1)
|
||||
{
|
||||
nextSegment = value.Length;
|
||||
}
|
||||
|
||||
ReadOnlySpan<char> heightSpan = value[..nextSegment];
|
||||
|
||||
if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
|
||||
&& int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
|
||||
{
|
||||
image.Width = width;
|
||||
image.Height = height;
|
||||
}
|
||||
|
||||
if (parts.Length >= 6)
|
||||
if (nextSegment < value.Length - 1)
|
||||
{
|
||||
image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|');
|
||||
value = value[(nextSegment + 1)..];
|
||||
var length = value.Length;
|
||||
|
||||
Span<char> blurHashSpan = stackalloc char[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var c = value[i];
|
||||
blurHashSpan[i] = c switch
|
||||
{
|
||||
'/' => '*',
|
||||
'\\' => '|',
|
||||
_ => c
|
||||
};
|
||||
}
|
||||
|
||||
image.BlurHash = new string(blurHashSpan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2110,27 +2158,6 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
private readonly ItemFields[] _allFields = Enum.GetValues<ItemFields>();
|
||||
|
||||
private string[] GetColumnNamesFromField(ItemFields field)
|
||||
{
|
||||
switch (field)
|
||||
{
|
||||
case ItemFields.Settings:
|
||||
return new[] { "IsLocked", "PreferredMetadataCountryCode", "PreferredMetadataLanguage", "LockedFields" };
|
||||
case ItemFields.ServiceName:
|
||||
return new[] { "ExternalServiceId" };
|
||||
case ItemFields.SortName:
|
||||
return new[] { "ForcedSortName" };
|
||||
case ItemFields.Taglines:
|
||||
return new[] { "Tagline" };
|
||||
case ItemFields.Tags:
|
||||
return new[] { "Tags" };
|
||||
case ItemFields.IsHD:
|
||||
return Array.Empty<string>();
|
||||
default:
|
||||
return new[] { field.ToString() };
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasField(InternalItemsQuery query, ItemFields name)
|
||||
{
|
||||
switch (name)
|
||||
|
@ -2319,9 +2346,32 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
if (!HasField(query, field))
|
||||
{
|
||||
foreach (var fieldToRemove in GetColumnNamesFromField(field))
|
||||
switch (field)
|
||||
{
|
||||
list.Remove(fieldToRemove);
|
||||
case ItemFields.Settings:
|
||||
list.Remove("IsLocked");
|
||||
list.Remove("PreferredMetadataCountryCode");
|
||||
list.Remove("PreferredMetadataLanguage");
|
||||
list.Remove("LockedFields");
|
||||
break;
|
||||
case ItemFields.ServiceName:
|
||||
list.Remove("ExternalServiceId");
|
||||
break;
|
||||
case ItemFields.SortName:
|
||||
list.Remove("ForcedSortName");
|
||||
break;
|
||||
case ItemFields.Taglines:
|
||||
list.Remove("Tagline");
|
||||
break;
|
||||
case ItemFields.Tags:
|
||||
list.Remove("Tags");
|
||||
break;
|
||||
case ItemFields.IsHD:
|
||||
// do nothing
|
||||
break;
|
||||
default:
|
||||
list.Remove(field.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -590,18 +590,9 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
|
||||
{
|
||||
var info = _openStreams.Values.FirstOrDefault(i =>
|
||||
{
|
||||
var liveStream = i as ILiveStream;
|
||||
if (liveStream != null)
|
||||
{
|
||||
return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return Task.FromResult(info as IDirectStreamProvider);
|
||||
return Task.FromResult(info.Value as IDirectStreamProvider);
|
||||
}
|
||||
|
||||
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
|
||||
|
|
|
@ -801,22 +801,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
if (string.IsNullOrWhiteSpace(path) || _activeRecordings.IsEmpty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var recording in _activeRecordings.Values)
|
||||
foreach (var (_, recordingInfo) in _activeRecordings)
|
||||
{
|
||||
if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested)
|
||||
if (string.Equals(recordingInfo.Path, path, StringComparison.Ordinal) && !recordingInfo.CancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
var timer = recording.Timer;
|
||||
var timer = recordingInfo.Timer;
|
||||
if (timer.Status != RecordingStatus.InProgress)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return recording;
|
||||
return recordingInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1621,9 +1621,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
|
||||
return _activeRecordings
|
||||
.Values
|
||||
.ToList()
|
||||
.Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
|
||||
.Any(i => string.Equals(i.Value.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Value.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private IRecorder GetRecorder(MediaSourceInfo mediaSource)
|
||||
|
|
|
@ -661,7 +661,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
_modelCache.Clear();
|
||||
}
|
||||
|
||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
|
||||
using var timedCancellationToken = new CancellationTokenSource(discoveryDurationMs);
|
||||
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timedCancellationToken.Token, cancellationToken);
|
||||
cancellationToken = linkedCancellationTokenSource.Token;
|
||||
var list = new List<TunerHostInfo>();
|
||||
|
||||
// Create udp broadcast discovery message
|
||||
|
|
|
@ -150,7 +150,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token;
|
||||
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token);
|
||||
cancellationToken = linkedCancellationTokenSource.Token;
|
||||
|
||||
// use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;
|
||||
|
|
|
@ -315,10 +315,9 @@ namespace Emby.Server.Implementations.Localization
|
|||
}
|
||||
|
||||
const string Prefix = "Core";
|
||||
var key = Prefix + culture;
|
||||
|
||||
return _dictionaries.GetOrAdd(
|
||||
key,
|
||||
culture,
|
||||
f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
|
|
|
@ -257,20 +257,17 @@ namespace Emby.Server.Implementations.QuickConnect
|
|||
}
|
||||
|
||||
// Expire stale connection requests
|
||||
var code = string.Empty;
|
||||
var values = _currentRequests.Values.ToList();
|
||||
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
foreach (var (_, currentRequest) in _currentRequests)
|
||||
{
|
||||
var added = values[i].DateAdded ?? DateTime.UnixEpoch;
|
||||
if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll)
|
||||
var added = currentRequest.DateAdded ?? DateTime.UnixEpoch;
|
||||
if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout))
|
||||
{
|
||||
code = values[i].Code;
|
||||
_logger.LogDebug("Removing expired request {code}", code);
|
||||
var code = currentRequest.Code;
|
||||
_logger.LogDebug("Removing expired request {Code}", code);
|
||||
|
||||
if (!_currentRequests.TryRemove(code, out _))
|
||||
{
|
||||
_logger.LogWarning("Request {code} already expired", code);
|
||||
_logger.LogWarning("Request {Code} already expired", code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -269,7 +269,9 @@ namespace Emby.Server.Implementations.SyncPlay
|
|||
var user = _userManager.GetUserById(session.UserId);
|
||||
List<GroupInfoDto> list = new List<GroupInfoDto>();
|
||||
|
||||
foreach (var group in _groups.Values)
|
||||
lock (_groupsLock)
|
||||
{
|
||||
foreach (var (_, group) in _groups)
|
||||
{
|
||||
// Locking required as group is not thread-safe.
|
||||
lock (group)
|
||||
|
@ -280,6 +282,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -71,7 +71,8 @@ namespace Jellyfin.Api.Helpers
|
|||
/// <returns>A <see cref="Task"/>.</returns>
|
||||
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token;
|
||||
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken);
|
||||
cancellationToken = linkedCancellationTokenSource.Token;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
95
MediaBrowser.Common/Extensions/SplitStringExtensions.cs
Normal file
95
MediaBrowser.Common/Extensions/SplitStringExtensions.cs
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Gérald Barré
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1034
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MediaBrowser.Common.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension class for splitting lines without unnecessary allocations.
|
||||
/// </summary>
|
||||
public static class SplitStringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new string split enumerator.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to split.</param>
|
||||
/// <param name="separator">The separator to split on.</param>
|
||||
/// <returns>The enumerator struct.</returns>
|
||||
[Pure]
|
||||
public static SplitEnumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new span split enumerator.
|
||||
/// </summary>
|
||||
/// <param name="str">The span to split.</param>
|
||||
/// <param name="separator">The separator to split on.</param>
|
||||
/// <returns>The enumerator struct.</returns>
|
||||
[Pure]
|
||||
public static SplitEnumerator Split(this ReadOnlySpan<char> str, char separator) => new (str, separator);
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public ref struct SplitEnumerator
|
||||
{
|
||||
private readonly char _separator;
|
||||
private ReadOnlySpan<char> _str;
|
||||
|
||||
public SplitEnumerator(ReadOnlySpan<char> str, char separator)
|
||||
{
|
||||
_str = str;
|
||||
_separator = separator;
|
||||
Current = default;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<char> Current { get; private set; }
|
||||
|
||||
public readonly SplitEnumerator GetEnumerator() => this;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_str.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var span = _str;
|
||||
var index = span.IndexOf(_separator);
|
||||
if (index == -1)
|
||||
{
|
||||
_str = ReadOnlySpan<char>.Empty;
|
||||
Current = span;
|
||||
return true;
|
||||
}
|
||||
|
||||
Current = span.Slice(0, index);
|
||||
_str = span[(index + 1)..];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1768,21 +1768,16 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
EnableImages = false
|
||||
}
|
||||
});
|
||||
}).TotalRecordCount;
|
||||
|
||||
double unplayedCount = unplayedQueryResult.TotalRecordCount;
|
||||
dto.UnplayedItemCount = unplayedQueryResult;
|
||||
|
||||
dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
|
||||
|
||||
if (itemDto != null && itemDto.RecursiveItemCount.HasValue)
|
||||
if (itemDto?.RecursiveItemCount > 0)
|
||||
{
|
||||
if (itemDto.RecursiveItemCount.Value > 0)
|
||||
{
|
||||
var unplayedPercentage = (unplayedCount / itemDto.RecursiveItemCount.Value) * 100;
|
||||
var unplayedPercentage = ((double)unplayedQueryResult / itemDto.RecursiveItemCount.Value) * 100;
|
||||
dto.PlayedPercentage = 100 - unplayedPercentage;
|
||||
dto.Played = dto.PlayedPercentage.Value >= 100;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dto.Played = (dto.UnplayedItemCount ?? 0) == 0;
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
|||
yield return new object[]
|
||||
{
|
||||
"/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
|
||||
new ItemImageInfo()
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
|
||||
Type = ImageType.Primary,
|
||||
|
@ -51,7 +51,27 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
|||
yield return new object[]
|
||||
{
|
||||
"https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0",
|
||||
new ItemImageInfo()
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
|
||||
Type = ImageType.Primary,
|
||||
}
|
||||
};
|
||||
|
||||
yield return new object[]
|
||||
{
|
||||
"https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary",
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
|
||||
Type = ImageType.Primary,
|
||||
}
|
||||
};
|
||||
|
||||
yield return new object[]
|
||||
{
|
||||
"https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*600",
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
|
||||
Type = ImageType.Primary,
|
||||
|
@ -61,7 +81,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
|||
yield return new object[]
|
||||
{
|
||||
"%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336",
|
||||
new ItemImageInfo()
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg",
|
||||
Type = ImageType.Primary,
|
||||
|
@ -88,6 +108,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
|||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("*")]
|
||||
[InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")]
|
||||
public void ItemImageInfoFromValueString_Invalid_Null(string value)
|
||||
{
|
||||
Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
|
||||
|
|
Loading…
Reference in New Issue
Block a user