Merge pull request #5938 from cvium/allocationz
This commit is contained in:
commit
48e81e65e8
|
@ -1002,15 +1002,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries);
|
foreach (var part in value.SpanSplit('|'))
|
||||||
|
|
||||||
foreach (var part in parts)
|
|
||||||
{
|
{
|
||||||
var idParts = part.Split('=');
|
var providerDelimiterIndex = part.IndexOf('=');
|
||||||
|
if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('='))
|
||||||
if (idParts.Length == 2)
|
|
||||||
{
|
{
|
||||||
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>();
|
return Array.Empty<ItemImageInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var list = new List<ItemImageInfo>();
|
var list = new List<ItemImageInfo>();
|
||||||
foreach (var part in parts)
|
foreach (var part in value.SpanSplit('|'))
|
||||||
{
|
{
|
||||||
var image = ItemImageInfoFromValueString(part);
|
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);
|
var nextSegment = value.IndexOf('*');
|
||||||
|
if (nextSegment == -1)
|
||||||
if (parts.Length < 3)
|
|
||||||
{
|
{
|
||||||
return null;
|
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);
|
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;
|
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)
|
value = value[(nextSegment + 1)..];
|
||||||
&& int.TryParse(parts[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
|
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.Width = width;
|
||||||
image.Height = height;
|
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 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)
|
private bool HasField(InternalItemsQuery query, ItemFields name)
|
||||||
{
|
{
|
||||||
switch (name)
|
switch (name)
|
||||||
|
@ -2319,9 +2346,32 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
if (!HasField(query, field))
|
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)
|
public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var info = _openStreams.Values.FirstOrDefault(i =>
|
var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
|
||||||
{
|
|
||||||
var liveStream = i as ILiveStream;
|
|
||||||
if (liveStream != null)
|
|
||||||
{
|
|
||||||
return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return Task.FromResult(info.Value as IDirectStreamProvider);
|
||||||
});
|
|
||||||
|
|
||||||
return Task.FromResult(info as IDirectStreamProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
|
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -801,22 +801,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
|
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(path))
|
if (string.IsNullOrWhiteSpace(path) || _activeRecordings.IsEmpty)
|
||||||
{
|
{
|
||||||
return null;
|
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)
|
if (timer.Status != RecordingStatus.InProgress)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return recording;
|
return recordingInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1621,9 +1621,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
}
|
}
|
||||||
|
|
||||||
return _activeRecordings
|
return _activeRecordings
|
||||||
.Values
|
.Any(i => string.Equals(i.Value.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Value.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
|
||||||
.ToList()
|
|
||||||
.Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRecorder GetRecorder(MediaSourceInfo mediaSource)
|
private IRecorder GetRecorder(MediaSourceInfo mediaSource)
|
||||||
|
|
|
@ -661,7 +661,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
_modelCache.Clear();
|
_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>();
|
var list = new List<TunerHostInfo>();
|
||||||
|
|
||||||
// Create udp broadcast discovery message
|
// Create udp broadcast discovery message
|
||||||
|
|
|
@ -150,7 +150,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
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
|
// 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;
|
var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;
|
||||||
|
|
|
@ -315,10 +315,9 @@ namespace Emby.Server.Implementations.Localization
|
||||||
}
|
}
|
||||||
|
|
||||||
const string Prefix = "Core";
|
const string Prefix = "Core";
|
||||||
var key = Prefix + culture;
|
|
||||||
|
|
||||||
return _dictionaries.GetOrAdd(
|
return _dictionaries.GetOrAdd(
|
||||||
key,
|
culture,
|
||||||
f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
|
f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -257,20 +257,17 @@ namespace Emby.Server.Implementations.QuickConnect
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expire stale connection requests
|
// Expire stale connection requests
|
||||||
var code = string.Empty;
|
foreach (var (_, currentRequest) in _currentRequests)
|
||||||
var values = _currentRequests.Values.ToList();
|
|
||||||
|
|
||||||
for (int i = 0; i < values.Count; i++)
|
|
||||||
{
|
{
|
||||||
var added = values[i].DateAdded ?? DateTime.UnixEpoch;
|
var added = currentRequest.DateAdded ?? DateTime.UnixEpoch;
|
||||||
if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll)
|
if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout))
|
||||||
{
|
{
|
||||||
code = values[i].Code;
|
var code = currentRequest.Code;
|
||||||
_logger.LogDebug("Removing expired request {code}", code);
|
_logger.LogDebug("Removing expired request {Code}", code);
|
||||||
|
|
||||||
if (!_currentRequests.TryRemove(code, out _))
|
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);
|
var user = _userManager.GetUserById(session.UserId);
|
||||||
List<GroupInfoDto> list = new List<GroupInfoDto>();
|
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.
|
// Locking required as group is not thread-safe.
|
||||||
lock (group)
|
lock (group)
|
||||||
|
@ -280,6 +282,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,8 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// <returns>A <see cref="Task"/>.</returns>
|
/// <returns>A <see cref="Task"/>.</returns>
|
||||||
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
|
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
|
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
|
EnableImages = false
|
||||||
}
|
}
|
||||||
});
|
}).TotalRecordCount;
|
||||||
|
|
||||||
double unplayedCount = unplayedQueryResult.TotalRecordCount;
|
dto.UnplayedItemCount = unplayedQueryResult;
|
||||||
|
|
||||||
dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
|
if (itemDto?.RecursiveItemCount > 0)
|
||||||
|
|
||||||
if (itemDto != null && itemDto.RecursiveItemCount.HasValue)
|
|
||||||
{
|
{
|
||||||
if (itemDto.RecursiveItemCount.Value > 0)
|
var unplayedPercentage = ((double)unplayedQueryResult / itemDto.RecursiveItemCount.Value) * 100;
|
||||||
{
|
|
||||||
var unplayedPercentage = (unplayedCount / itemDto.RecursiveItemCount.Value) * 100;
|
|
||||||
dto.PlayedPercentage = 100 - unplayedPercentage;
|
dto.PlayedPercentage = 100 - unplayedPercentage;
|
||||||
dto.Played = dto.PlayedPercentage.Value >= 100;
|
dto.Played = dto.PlayedPercentage.Value >= 100;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dto.Played = (dto.UnplayedItemCount ?? 0) == 0;
|
dto.Played = (dto.UnplayedItemCount ?? 0) == 0;
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
||||||
yield return new object[]
|
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",
|
"/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",
|
Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
|
||||||
Type = ImageType.Primary,
|
Type = ImageType.Primary,
|
||||||
|
@ -51,7 +51,27 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
||||||
yield return new object[]
|
yield return new object[]
|
||||||
{
|
{
|
||||||
"https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0",
|
"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",
|
Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
|
||||||
Type = ImageType.Primary,
|
Type = ImageType.Primary,
|
||||||
|
@ -61,7 +81,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
||||||
yield return new object[]
|
yield return new object[]
|
||||||
{
|
{
|
||||||
"%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336",
|
"%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336",
|
||||||
new ItemImageInfo()
|
new ItemImageInfo
|
||||||
{
|
{
|
||||||
Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg",
|
Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg",
|
||||||
Type = ImageType.Primary,
|
Type = ImageType.Primary,
|
||||||
|
@ -88,6 +108,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("")]
|
[InlineData("")]
|
||||||
[InlineData("*")]
|
[InlineData("*")]
|
||||||
|
[InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")]
|
||||||
public void ItemImageInfoFromValueString_Invalid_Null(string value)
|
public void ItemImageInfoFromValueString_Invalid_Null(string value)
|
||||||
{
|
{
|
||||||
Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
|
Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user