Merge pull request #5938 from cvium/allocationz

This commit is contained in:
Bond-009 2021-05-05 09:35:30 +02:00 committed by GitHub
commit 48e81e65e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 253 additions and 100 deletions

View File

@ -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;
} }
} }
} }

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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());
} }

View File

@ -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);
} }
} }
} }

View File

@ -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;
} }

View File

@ -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
{ {

View 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;
}
}
}
}

View File

@ -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;

View File

@ -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));