Browse Source

Merge pull request #5938 from cvium/allocationz

Bond-009 4 years ago
parent
commit
48e81e65e8

+ 97 - 47
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -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);
+            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)
+            {
+                nextSegment = value.Length;
+            }
 
-            image.Path = RestorePath(parts[0]);
+            ReadOnlySpan<char> imageType = value[..nextSegment];
 
-            if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
+            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;
                     }
                 }
             }
@@ -2568,9 +2618,9 @@ namespace Emby.Server.Implementations.Data
             }
 
             var commandText = "select "
-                            + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
-                            + GetFromText()
-                            + GetJoinUserDataText(query);
+                              + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+                              + GetFromText()
+                              + GetJoinUserDataText(query);
 
             var whereClauses = GetWhereClauses(query, null);
             if (whereClauses.Count != 0)

+ 2 - 11
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)

+ 6 - 8
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)

+ 3 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

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

+ 2 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs

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

+ 1 - 2
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());
         }
 

+ 6 - 9
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);
                     }
                 }
             }

+ 8 - 5
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());
+                        }
                     }
                 }
             }

+ 2 - 1
Jellyfin.Api/Helpers/ProgressiveFileCopier.cs

@@ -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 - 0
MediaBrowser.Common/Extensions/SplitStringExtensions.cs

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

+ 6 - 11
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
                 {

+ 24 - 3
tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs

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