aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs153
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs13
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs14
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs3
-rw-r--r--Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs15
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs13
-rw-r--r--MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs102
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs17
-rw-r--r--MediaBrowser.Model/Entities/ProviderIdsExtensions.cs30
9 files changed, 266 insertions, 94 deletions
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 28e59913c..bd5a3e30b 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -1007,15 +1007,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), part.Slice(providerDelimiterIndex + 1));
}
}
}
@@ -1057,9 +1054,8 @@ namespace Emby.Server.Implementations.Data
return;
}
- 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);
@@ -1094,41 +1090,89 @@ namespace Emby.Server.Implementations.Data
.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)
+ {
+ return null;
+ }
- if (parts.Length < 3)
+ ReadOnlySpan<char> path = value[..nextSegment];
+ value = value[(nextSegment + 1)..];
+ nextSegment = value.IndexOf('*');
+ if (nextSegment == -1)
{
return null;
}
- var image = new ItemImageInfo();
+ ReadOnlySpan<char> dateModified = value[..nextSegment];
+ value = value[(nextSegment + 1)..];
+ nextSegment = value.IndexOf('*');
+ if (nextSegment == -1)
+ {
+ return null;
+ }
+
+ ReadOnlySpan<char> imageType = value[..nextSegment];
- image.Path = RestorePath(parts[0]);
+ var image = new ItemImageInfo
+ {
+ Path = RestorePath(path.ToString())
+ };
- if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
+ 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('*');
+ 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.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 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)
@@ -2327,9 +2350,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;
}
}
}
@@ -2575,10 +2621,21 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
- var commandText = "select "
- + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
- + GetFromText()
- + GetJoinUserDataText(query);
+ var commandText = "select ";
+ if (EnableGroupByPresentationUniqueKey(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);
if (whereClauses.Count != 0)
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index d0b85f07d..85d6d3043 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -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);
- }
-
- return false;
- });
+ var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
- return Task.FromResult(info as IDirectStreamProvider);
+ return Task.FromResult(info.Value as IDirectStreamProvider);
}
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index c9d9cc49a..665fbfa0f 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -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)
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 2fdc2b4d9..46858b4fb 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -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());
}
diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
index 22739a008..0259dc436 100644
--- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
+++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
@@ -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);
}
}
}
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
index 315277985..72c0a838e 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
@@ -269,14 +269,17 @@ 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)
{
- // Locking required as group is not thread-safe.
- lock (group)
+ foreach (var (_, group) in _groups)
{
- 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());
+ }
}
}
}
diff --git a/MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs b/MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs
new file mode 100644
index 000000000..5332aba9f
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs
@@ -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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index d45f8758c..d74e6f9d8 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1768,20 +1768,15 @@ namespace MediaBrowser.Controller.Entities
{
EnableImages = false
}
- });
-
- double unplayedCount = unplayedQueryResult.TotalRecordCount;
+ }).TotalRecordCount;
- dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
+ dto.UnplayedItemCount = unplayedQueryResult;
- if (itemDto != null && itemDto.RecursiveItemCount.HasValue)
+ if (itemDto?.RecursiveItemCount > 0)
{
- if (itemDto.RecursiveItemCount.Value > 0)
- {
- var unplayedPercentage = (unplayedCount / itemDto.RecursiveItemCount.Value) * 100;
- dto.PlayedPercentage = 100 - unplayedPercentage;
- dto.Played = dto.PlayedPercentage.Value >= 100;
- }
+ var unplayedPercentage = ((double)unplayedQueryResult / itemDto.RecursiveItemCount.Value) * 100;
+ dto.PlayedPercentage = 100 - unplayedPercentage;
+ dto.Played = dto.PlayedPercentage.Value >= 100;
}
else
{
diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
index 09d14dc6a..bc14da7f2 100644
--- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
+++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
@@ -136,6 +136,36 @@ namespace MediaBrowser.Model.Entities
/// 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>
+ /// Sets a provider id.
+ /// </summary>
+ /// <param name="instance">The instance.</param>
/// <param name="provider">The provider.</param>
/// <param name="value">The value.</param>
public static void SetProviderId(this IHasProviderIds instance, MetadataProvider provider, string value)