mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Reduce some allocations with the magic of spans etc.
This commit is contained in:
parent
eeb5d4bd1e
commit
608cba817c
@ -1007,15 +1007,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), part.Slice(providerDelimiterIndex + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1057,9 +1054,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@ -1094,41 +1090,89 @@ namespace Emby.Server.Implementations.Data
|
|||||||
.Append(hash.Replace('*', '/').Replace('|', '\\'));
|
.Append(hash.Replace('*', '/').Replace('|', '\\'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
private 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)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
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('*');
|
||||||
|
ReadOnlySpan<char> widthSpan = value[..nextSegment];
|
||||||
|
|
||||||
|
value = value[(nextSegment + 1)..];
|
||||||
|
nextSegment = value.IndexOf('*');
|
||||||
|
if (nextSegment == -1)
|
||||||
|
{
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
nextSegment += 1;
|
||||||
|
if (nextSegment < value.Length - 1)
|
||||||
{
|
{
|
||||||
image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|');
|
value = value[nextSegment..];
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2118,27 +2162,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)
|
||||||
@ -2327,9 +2350,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2575,10 +2621,21 @@ namespace Emby.Server.Implementations.Data
|
|||||||
query.Limit = query.Limit.Value + 4;
|
query.Limit = query.Limit.Value + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandText = "select "
|
var commandText = "select ";
|
||||||
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
|
if (EnableGroupByPresentationUniqueKey(query))
|
||||||
+ GetFromText()
|
{
|
||||||
+ GetJoinUserDataText(query);
|
commandText += "count (distinct PresentationUniqueKey)";
|
||||||
|
}
|
||||||
|
else if (query.GroupBySeriesPresentationUniqueKey)
|
||||||
|
{
|
||||||
|
commandText += "count (distinct SeriesPresentationUniqueKey)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
commandText += "count (guid)";
|
||||||
|
}
|
||||||
|
|
||||||
|
commandText += GetFromText() + GetJoinUserDataText(query);
|
||||||
|
|
||||||
var whereClauses = GetWhereClauses(query, null);
|
var whereClauses = GetWhereClauses(query, null);
|
||||||
if (whereClauses.Count != 0)
|
if (whereClauses.Count != 0)
|
||||||
|
@ -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)
|
||||||
|
@ -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,14 +269,17 @@ 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)
|
||||||
{
|
{
|
||||||
// Locking required as group is not thread-safe.
|
foreach (var (_, group) in _groups)
|
||||||
lock (group)
|
|
||||||
{
|
{
|
||||||
if (group.HasAccessToPlayQueue(user))
|
// Locking required as group is not thread-safe.
|
||||||
|
lock (group)
|
||||||
{
|
{
|
||||||
list.Add(group.GetInfo());
|
if (group.HasAccessToPlayQueue(user))
|
||||||
|
{
|
||||||
|
list.Add(group.GetInfo());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
102
MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs
Normal file
102
MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
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 SplitLinesStringExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new line 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 LineSplitEnumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new line 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 LineSplitEnumerator Split(this ReadOnlySpan<char> str, char separator) => new (str, separator);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Auto)]
|
||||||
|
public ref struct LineSplitEnumerator
|
||||||
|
{
|
||||||
|
private readonly char _separator;
|
||||||
|
private ReadOnlySpan<char> _str;
|
||||||
|
|
||||||
|
public LineSplitEnumerator(ReadOnlySpan<char> str, char separator)
|
||||||
|
{
|
||||||
|
_str = str;
|
||||||
|
_separator = separator;
|
||||||
|
Current = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<char> Current { get; private set; }
|
||||||
|
|
||||||
|
public readonly LineSplitEnumerator 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < span.Length - 1 && span[index] == _separator)
|
||||||
|
{
|
||||||
|
Current = span.Slice(0, index);
|
||||||
|
_str = span[(index + 1)..];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = span.Slice(0, index);
|
||||||
|
_str = span[(index + 1)..];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1768,20 +1768,15 @@ 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;
|
||||||
{
|
dto.PlayedPercentage = 100 - unplayedPercentage;
|
||||||
var unplayedPercentage = (unplayedCount / itemDto.RecursiveItemCount.Value) * 100;
|
dto.Played = dto.PlayedPercentage.Value >= 100;
|
||||||
dto.PlayedPercentage = 100 - unplayedPercentage;
|
|
||||||
dto.Played = dto.PlayedPercentage.Value >= 100;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -132,6 +132,36 @@ namespace MediaBrowser.Model.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a provider id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">The instance.</param>
|
||||||
|
/// <param name="name">The name.</param>
|
||||||
|
/// <param name="value">The value.</param>
|
||||||
|
public static void SetProviderId(this IHasProviderIds instance, ReadOnlySpan<char> name, ReadOnlySpan<char> value)
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(instance));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's null remove the key from the dictionary
|
||||||
|
if (value.IsEmpty)
|
||||||
|
{
|
||||||
|
instance.ProviderIds?.Remove(name.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ensure it exists
|
||||||
|
if (instance.ProviderIds == null)
|
||||||
|
{
|
||||||
|
instance.ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.ProviderIds[name.ToString()] = value.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a provider id.
|
/// Sets a provider id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user