diff options
26 files changed, 237 insertions, 447 deletions
diff --git a/.gitmodules b/.gitmodules index c10f5905c..2b97b1331 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "MediaBrowser.WebDashboard/jellyfin-web"] path = MediaBrowser.WebDashboard/jellyfin-web url = https://github.com/jellyfin/jellyfin-web.git + branch = . diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 758202af6..0831e1340 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -21,6 +21,7 @@ - [WillWill56](https://github.com/WillWill56) - [Liggy](https://github.com/Liggy) - [fruhnow](https://github.com/fruhnow) + - [Lynxy](https://github.com/Lynxy) # Emby Contributors diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e9f43a0ea..b5a64cbdd 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -550,16 +550,18 @@ namespace Emby.Server.Implementations var entryPoints = GetExports<IServerEntryPoint>(); - var now = DateTime.UtcNow; + var stopWatch = new Stopwatch(); + stopWatch.Start(); await Task.WhenAll(StartEntryPoints(entryPoints, true)); - Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); + Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; - now = DateTime.UtcNow; + stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)); - Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); + Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); + stopWatch.Stop(); } private IEnumerable<Task> StartEntryPoints(IEnumerable<IServerEntryPoint> entryPoints, bool isBeforeStartup) diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index ad6c537ef..3c7cbb115 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -35,64 +35,52 @@ namespace Emby.Server.Implementations.Channels public static string GetUserDistinctValue(User user) { var channels = user.Policy.EnabledChannels - .OrderBy(i => i) - .ToList(); + .OrderBy(i => i); - return string.Join("|", channels.ToArray()); + return string.Join("|", channels); } private void CleanDatabase(CancellationToken cancellationToken) { var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds(); - var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery + var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(Channel).Name } + IncludeItemTypes = new[] { typeof(Channel).Name }, + ExcludeItemIds = installedChannelIds.ToArray() }); - var invalidIds = databaseIds - .Except(installedChannelIds) - .ToList(); - - foreach (var id in invalidIds) + foreach (var channel in uninstalledChannels) { cancellationToken.ThrowIfCancellationRequested(); - CleanChannel(id, cancellationToken); + CleanChannel((Channel)channel, cancellationToken); } } - private void CleanChannel(Guid id, CancellationToken cancellationToken) + private void CleanChannel(Channel channel, CancellationToken cancellationToken) { - _logger.LogInformation("Cleaning channel {0} from database", id); + _logger.LogInformation("Cleaning channel {0} from database", channel.Id); // Delete all channel items - var allIds = _libraryManager.GetItemIds(new InternalItemsQuery + var items = _libraryManager.GetItemList(new InternalItemsQuery { - ChannelIds = new[] { id } + ChannelIds = new[] { channel.Id } }); - foreach (var deleteId in allIds) + foreach (var item in items) { cancellationToken.ThrowIfCancellationRequested(); - DeleteItem(deleteId); - } - - // Finally, delete the channel itself - DeleteItem(id); - } + _libraryManager.DeleteItem(item, new DeleteOptions + { + DeleteFileLocation = false - private void DeleteItem(Guid id) - { - var item = _libraryManager.GetItemById(id); - - if (item == null) - { - return; + }, false); } - _libraryManager.DeleteItem(item, new DeleteOptions + // Finally, delete the channel itself + _libraryManager.DeleteItem(channel, new DeleteOptions { DeleteFileLocation = false diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 6502e4aed..70e5fa640 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2747,7 +2747,7 @@ namespace Emby.Server.Implementations.Data if (elapsed >= slowThreshold) { - Logger.LogWarning("{0} query time (slow): {1}ms. Query: {2}", + Logger.LogWarning("{0} query time (slow): {1:g}. Query: {2}", methodName, elapsed, commandText); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 2233d3d40..7b28a22a8 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -5,8 +5,6 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -21,8 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; @@ -83,15 +79,8 @@ namespace Emby.Server.Implementations.Dto return GetBaseItemDto(item, options, user, owner); } - public BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) - { - return GetBaseItemDtos(items, items.Count, options, user, owner); - } - - public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null) - { - return GetBaseItemDtos(items, items.Length, options, user, owner); - } + public BaseItemDto[] GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) + => GetBaseItemDtos(items, items.Count, options, user, owner); public BaseItemDto[] GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) { diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 2232b3eeb..2e0728136 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -539,21 +539,10 @@ namespace Emby.Server.Implementations.HttpClientManager var contentLength = GetContentLength(httpResponse); - if (contentLength.HasValue) - { - using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) - { - await httpResponse.GetResponseStream().CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); - } - } - else + using (var stream = httpResponse.GetResponseStream()) + using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) { - // We're not able to track progress - using (var stream = httpResponse.GetResponseStream()) - using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); - } + await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } options.Progress.Report(100); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index d78891ac7..ee746c669 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -67,7 +68,7 @@ namespace Emby.Server.Implementations.HttpServer _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; - + _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; @@ -286,31 +287,6 @@ namespace Emby.Server.Implementations.HttpServer } } - private static readonly string[] _skipLogExtensions = - { - ".js", - ".css", - ".woff", - ".woff2", - ".ttf", - ".html" - }; - - private bool EnableLogging(string url, string localPath) - { - var extension = GetExtension(url); - - return ((string.IsNullOrEmpty(extension) || !_skipLogExtensions.Contains(extension)) - && (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1)); - } - - private static string GetExtension(string url) - { - var parts = url.Split(new[] { '?' }, 2); - - return Path.GetExtension(parts[0]); - } - public static string RemoveQueryStringByKey(string url, string key) { var uri = new Uri(url); @@ -448,10 +424,9 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { - var date = DateTime.Now; + var stopWatch = new Stopwatch(); + stopWatch.Start(); var httpRes = httpReq.Response; - bool enableLog = false; - bool logHeaders = false; string urlToLog = null; string remoteIp = httpReq.RemoteIp; @@ -498,18 +473,8 @@ namespace Emby.Server.Implementations.HttpServer return; } - var operationName = httpReq.OperationName; - - enableLog = EnableLogging(urlString, localPath); - urlToLog = urlString; - logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1; - - if (enableLog) - { - urlToLog = GetUrlToLog(urlString); - - LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null); - } + urlToLog = GetUrlToLog(urlString); + Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers); if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) @@ -517,6 +482,7 @@ namespace Emby.Server.Implementations.HttpServer RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) { @@ -562,16 +528,19 @@ namespace Emby.Server.Implementations.HttpServer RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) { RedirectToUrl(httpRes, "../" + DefaultRedirectPath); return; } + if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { RedirectToUrl(httpRes, DefaultRedirectPath); return; } + if (string.IsNullOrEmpty(localPath)) { RedirectToUrl(httpRes, "/" + DefaultRedirectPath); @@ -607,33 +576,21 @@ namespace Emby.Server.Implementations.HttpServer if (handler != null) { - await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false); + await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, httpReq.OperationName, cancellationToken).ConfigureAwait(false); } else { await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false); } } - catch (OperationCanceledException ex) - { - await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); - } - - catch (IOException ex) - { - await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); - } - - catch (SocketException ex) + catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException) { await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); } - catch (SecurityException ex) { await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false); } - catch (Exception ex) { var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase); @@ -644,13 +601,15 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.Close(); - if (enableLog) + stopWatch.Stop(); + var elapsed = stopWatch.Elapsed; + if (elapsed.TotalMilliseconds > 500) { - var statusCode = httpRes.StatusCode; - - var duration = DateTime.Now - date; - - LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null); + _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); + } + else + { + _logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } } } @@ -663,12 +622,11 @@ namespace Emby.Server.Implementations.HttpServer var pathParts = pathInfo.TrimStart('/').Split('/'); if (pathParts.Length == 0) { - _logger.LogError("Path parts empty for PathInfo: {pathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); + _logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); return null; } var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out string contentType); - if (restPath != null) { return new ServiceHandler diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs deleted file mode 100644 index d22d9db26..000000000 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Globalization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.HttpServer -{ - public static class LoggerUtils - { - public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers) - { - if (headers == null) - { - logger.LogInformation("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty); - } - else - { - var headerText = string.Empty; - var index = 0; - - foreach (var i in headers) - { - if (index > 0) - { - headerText += ", "; - } - - headerText += i.Name + "=" + i.Value; - - index++; - } - - logger.LogInformation("HTTP {0} {1}. {2}", method, url, headerText); - } - } - - /// <summary> - /// Logs the response. - /// </summary> - /// <param name="logger">The logger.</param> - /// <param name="statusCode">The status code.</param> - /// <param name="url">The URL.</param> - /// <param name="endPoint">The end point.</param> - /// <param name="duration">The duration.</param> - public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration, QueryParamCollection headers) - { - var durationMs = duration.TotalMilliseconds; - var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; - - //var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); - var headerText = string.Empty; - logger.LogInformation("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText); - } - } -} diff --git a/Emby.Server.Implementations/Localization/Ratings/kz.csv b/Emby.Server.Implementations/Localization/Ratings/kz.csv index 4441c5650..d546bff53 100644 --- a/Emby.Server.Implementations/Localization/Ratings/kz.csv +++ b/Emby.Server.Implementations/Localization/Ratings/kz.csv @@ -1,6 +1,7 @@ -KZ-К,1 -KZ-БА,6 -KZ-Б14,7 -KZ-Е16,8 -KZ-Е18,10 -KZ-НА,15 +KZ-6-,0 +KZ-6+,6 +KZ-12+,12 +KZ-14+,14 +KZ-16+,16 +KZ-18+,18 +KZ-21+,21 diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 98685cebe..ec9466c4a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var dueTime = triggerDate - now; - logger.LogInformation("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:g}, which is {DueTime:g} from now.", taskName, triggerDate, dueTime); Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index f575baca3..ccb28e8df 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Services private const char ComponentSeperator = '.'; private const string VariablePrefix = "{"; - readonly bool[] componentsWithSeparators; + private readonly bool[] componentsWithSeparators; private readonly string restPath; public bool IsWildCardPath { get; private set; } @@ -54,10 +54,6 @@ namespace Emby.Server.Implementations.Services public string Description { get; private set; } public bool IsHidden { get; private set; } - public int Priority { get; set; } //passed back to RouteAttribute - - public IEnumerable<string> PathVariables => this.variablesNames.Where(e => !string.IsNullOrWhiteSpace(e)); - public static string[] GetPathPartsForMatching(string pathInfo) { return pathInfo.ToLowerInvariant().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); @@ -83,9 +79,12 @@ namespace Emby.Server.Implementations.Services { list.Add(hashPrefix + part); - var subParts = part.Split(ComponentSeperator); - if (subParts.Length == 1) continue; + if (part.IndexOf(ComponentSeperator) == -1) + { + continue; + } + var subParts = part.Split(ComponentSeperator); foreach (var subPart in subParts) { list.Add(hashPrefix + subPart); @@ -114,7 +113,7 @@ namespace Emby.Server.Implementations.Services { if (string.IsNullOrEmpty(component)) continue; - if (StringContains(component, VariablePrefix) + if (component.IndexOf(VariablePrefix, StringComparison.OrdinalIgnoreCase) != -1 && component.IndexOf(ComponentSeperator) != -1) { hasSeparators.Add(true); @@ -165,7 +164,11 @@ namespace Emby.Server.Implementations.Services for (var i = 0; i < components.Length - 1; i++) { - if (!this.isWildcard[i]) continue; + if (!this.isWildcard[i]) + { + continue; + } + if (this.literalsToMatch[i + 1] == null) { throw new ArgumentException( @@ -173,7 +176,7 @@ namespace Emby.Server.Implementations.Services } } - this.wildcardCount = this.isWildcard.Count(x => x); + this.wildcardCount = this.isWildcard.Length; this.IsWildCardPath = this.wildcardCount > 0; this.FirstMatchHashKey = !this.IsWildCardPath @@ -181,19 +184,14 @@ namespace Emby.Server.Implementations.Services : WildCardChar + PathSeperator + firstLiteralMatch; this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType); - RegisterCaseInsenstivePropertyNameMappings(); - } - private void RegisterCaseInsenstivePropertyNameMappings() - { - foreach (var propertyInfo in GetSerializableProperties(RequestType)) - { - var propertyName = propertyInfo.Name; - propertyNamesMap.Add(propertyName.ToLowerInvariant(), propertyName); - } + _propertyNamesMap = new HashSet<string>( + GetSerializableProperties(RequestType).Select(x => x.Name), + StringComparer.OrdinalIgnoreCase); } - internal static string[] IgnoreAttributesNamed = new[] { + internal static string[] IgnoreAttributesNamed = new[] + { "IgnoreDataMemberAttribute", "JsonIgnoreAttribute" }; @@ -201,19 +199,12 @@ namespace Emby.Server.Implementations.Services private static Type excludeType = typeof(Stream); - internal static List<PropertyInfo> GetSerializableProperties(Type type) + internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type) { - var list = new List<PropertyInfo>(); - var props = GetPublicProperties(type); - - foreach (var prop in props) + foreach (var prop in GetPublicProperties(type)) { - if (prop.GetMethod == null) - { - continue; - } - - if (excludeType == prop.PropertyType) + if (prop.GetMethod == null + || excludeType == prop.PropertyType) { continue; } @@ -230,23 +221,21 @@ namespace Emby.Server.Implementations.Services if (!ignored) { - list.Add(prop); + yield return prop; } } - - // else return those properties that are not decorated with IgnoreDataMember - return list; } - private static List<PropertyInfo> GetPublicProperties(Type type) + private static IEnumerable<PropertyInfo> GetPublicProperties(Type type) { - if (type.GetTypeInfo().IsInterface) + if (type.IsInterface) { var propertyInfos = new List<PropertyInfo>(); - - var considered = new List<Type>(); + var considered = new List<Type>() + { + type + }; var queue = new Queue<Type>(); - considered.Add(type); queue.Enqueue(type); while (queue.Count > 0) @@ -254,15 +243,16 @@ namespace Emby.Server.Implementations.Services var subType = queue.Dequeue(); foreach (var subInterface in subType.GetTypeInfo().ImplementedInterfaces) { - if (considered.Contains(subInterface)) continue; + if (considered.Contains(subInterface)) + { + continue; + } considered.Add(subInterface); queue.Enqueue(subInterface); } - var typeProperties = GetTypesPublicProperties(subType); - - var newPropertyInfos = typeProperties + var newPropertyInfos = GetTypesPublicProperties(subType) .Where(x => !propertyInfos.Contains(x)); propertyInfos.InsertRange(0, newPropertyInfos); @@ -271,28 +261,22 @@ namespace Emby.Server.Implementations.Services return propertyInfos; } - var list = new List<PropertyInfo>(); - - foreach (var t in GetTypesPublicProperties(type)) - { - if (t.GetIndexParameters().Length == 0) - { - list.Add(t); - } - } - return list; + return GetTypesPublicProperties(type) + .Where(x => x.GetIndexParameters().Length == 0); } - private static PropertyInfo[] GetTypesPublicProperties(Type subType) + private static IEnumerable<PropertyInfo> GetTypesPublicProperties(Type subType) { - var pis = new List<PropertyInfo>(); foreach (var pi in subType.GetRuntimeProperties()) { var mi = pi.GetMethod ?? pi.SetMethod; - if (mi != null && mi.IsStatic) continue; - pis.Add(pi); + if (mi != null && mi.IsStatic) + { + continue; + } + + yield return pi; } - return pis.ToArray(); } /// <summary> @@ -302,7 +286,7 @@ namespace Emby.Server.Implementations.Services private readonly StringMapTypeDeserializer typeDeserializer; - private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>(); + private readonly HashSet<string> _propertyNamesMap; public int MatchScore(string httpMethod, string[] withPathInfoParts) { @@ -312,13 +296,10 @@ namespace Emby.Server.Implementations.Services return -1; } - var score = 0; - //Routes with least wildcard matches get the highest score - score += Math.Max((100 - wildcardMatchCount), 1) * 1000; - - //Routes with less variable (and more literal) matches - score += Math.Max((10 - VariableArgsCount), 1) * 100; + var score = Math.Max((100 - wildcardMatchCount), 1) * 1000 + //Routes with less variable (and more literal) matches + + Math.Max((10 - VariableArgsCount), 1) * 100; //Exact verb match is better than ANY if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase)) @@ -333,11 +314,6 @@ namespace Emby.Server.Implementations.Services return score; } - private bool StringContains(string str1, string str2) - { - return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1; - } - /// <summary> /// For performance withPathInfoParts should already be a lower case string /// to minimize redundant matching operations. @@ -374,7 +350,8 @@ namespace Emby.Server.Implementations.Services if (i < this.TotalComponentsCount - 1) { // Continue to consume up until a match with the next literal - while (pathIx < withPathInfoParts.Length && !LiteralsEqual(withPathInfoParts[pathIx], this.literalsToMatch[i + 1])) + while (pathIx < withPathInfoParts.Length + && !string.Equals(withPathInfoParts[pathIx], this.literalsToMatch[i + 1], StringComparison.InvariantCultureIgnoreCase)) { pathIx++; wildcardMatchCount++; @@ -403,10 +380,12 @@ namespace Emby.Server.Implementations.Services continue; } - if (withPathInfoParts.Length <= pathIx || !LiteralsEqual(withPathInfoParts[pathIx], literalToMatch)) + if (withPathInfoParts.Length <= pathIx + || !string.Equals(withPathInfoParts[pathIx], literalToMatch, StringComparison.InvariantCultureIgnoreCase)) { return false; } + pathIx++; } } @@ -414,35 +393,26 @@ namespace Emby.Server.Implementations.Services return pathIx == withPathInfoParts.Length; } - private static bool LiteralsEqual(string str1, string str2) - { - // Most cases - if (string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - // Handle turkish i - str1 = str1.ToUpperInvariant(); - str2 = str2.ToUpperInvariant(); - - // Invariant IgnoreCase would probably be better but it's not available in PCL - return string.Equals(str1, str2, StringComparison.CurrentCultureIgnoreCase); - } - private bool ExplodeComponents(ref string[] withPathInfoParts) { var totalComponents = new List<string>(); for (var i = 0; i < withPathInfoParts.Length; i++) { var component = withPathInfoParts[i]; - if (string.IsNullOrEmpty(component)) continue; + if (string.IsNullOrEmpty(component)) + { + continue; + } if (this.PathComponentsCount != this.TotalComponentsCount && this.componentsWithSeparators[i]) { var subComponents = component.Split(ComponentSeperator); - if (subComponents.Length < 2) return false; + if (subComponents.Length < 2) + { + return false; + } + totalComponents.AddRange(subComponents); } else @@ -483,7 +453,7 @@ namespace Emby.Server.Implementations.Services continue; } - if (!this.propertyNamesMap.TryGetValue(variableName.ToLowerInvariant(), out var propertyNameOnRequest)) + if (!this._propertyNamesMap.Contains(variableName)) { if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase)) { @@ -507,6 +477,7 @@ namespace Emby.Server.Implementations.Services { sb.Append(PathSeperatorChar + requestComponents[j]); } + value = sb.ToString(); } else @@ -517,13 +488,13 @@ namespace Emby.Server.Implementations.Services var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1]; if (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) { - var sb = new StringBuilder(); - sb.Append(value); + var sb = new StringBuilder(value); pathIx++; while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) { sb.Append(PathSeperatorChar + requestComponents[pathIx++]); } + value = sb.ToString(); } else @@ -538,7 +509,7 @@ namespace Emby.Server.Implementations.Services pathIx++; } - requestKeyValuesMap[propertyNameOnRequest] = value; + requestKeyValuesMap[variableName] = value; } if (queryStringAndFormData != null) diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index d13935fba..f835aa1b5 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -11,15 +11,16 @@ namespace Emby.Server.Implementations.Services { internal class PropertySerializerEntry { - public PropertySerializerEntry(Action<object, object> propertySetFn, Func<string, object> propertyParseStringFn) + public PropertySerializerEntry(Action<object, object> propertySetFn, Func<string, object> propertyParseStringFn, Type propertyType) { PropertySetFn = propertySetFn; PropertyParseStringFn = propertyParseStringFn; + PropertyType = PropertyType; } - public Action<object, object> PropertySetFn; - public Func<string, object> PropertyParseStringFn; - public Type PropertyType; + public Action<object, object> PropertySetFn { get; private set; } + public Func<string, object> PropertyParseStringFn { get; private set; } + public Type PropertyType { get; private set; } } private readonly Type type; @@ -29,7 +30,9 @@ namespace Emby.Server.Implementations.Services public Func<string, object> GetParseFn(Type propertyType) { if (propertyType == typeof(string)) + { return s => s; + } return _GetParseFn(propertyType); } @@ -48,7 +51,7 @@ namespace Emby.Server.Implementations.Services var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo); var propertyType = propertyInfo.PropertyType; var propertyParseStringFn = GetParseFn(propertyType); - var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType }; + var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType); propertySetterMap[propertyInfo.Name] = propertySerializer; } @@ -56,34 +59,21 @@ namespace Emby.Server.Implementations.Services public object PopulateFromMap(object instance, IDictionary<string, string> keyValuePairs) { - string propertyName = null; - string propertyTextValue = null; PropertySerializerEntry propertySerializerEntry = null; if (instance == null) + { instance = _CreateInstanceFn(type); + } foreach (var pair in keyValuePairs) { - propertyName = pair.Key; - propertyTextValue = pair.Value; - - if (string.IsNullOrEmpty(propertyTextValue)) - { - continue; - } + string propertyName = pair.Key; + string propertyTextValue = pair.Value; - if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)) - { - if (propertyName == "v") - { - continue; - } - - continue; - } - - if (propertySerializerEntry.PropertySetFn == null) + if (string.IsNullOrEmpty(propertyTextValue) + || !propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry) + || propertySerializerEntry.PropertySetFn == null) { continue; } @@ -99,6 +89,7 @@ namespace Emby.Server.Implementations.Services { continue; } + propertySerializerEntry.PropertySetFn(instance, value); } @@ -107,7 +98,11 @@ namespace Emby.Server.Implementations.Services public static string LeftPart(string strVal, char needle) { - if (strVal == null) return null; + if (strVal == null) + { + return null; + } + var pos = strVal.IndexOf(needle); return pos == -1 ? strVal @@ -119,7 +114,10 @@ namespace Emby.Server.Implementations.Services { public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) { - if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null; + if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) + { + return null; + } var setMethodInfo = propertyInfo.SetMethod; return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index fa0ab62d3..03e7b2654 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1090,7 +1090,7 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } - private IList<BaseItem> TranslateItemForPlayback(Guid id, User user) + private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index f2a08c9ae..8396ad600 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Server.SocketSharp { public partial class WebSocketSharpRequest : IHttpRequest { - internal static string GetParameter(string header, string attr) + internal static string GetParameter(ReadOnlySpan<char> header, string attr) { int ap = header.IndexOf(attr, StringComparison.Ordinal); if (ap == -1) @@ -31,13 +31,14 @@ namespace Jellyfin.Server.SocketSharp ending = ' '; } - int end = header.IndexOf(ending, ap + 1); + var slice = header.Slice(ap + 1); + int end = slice.IndexOf(ending); if (end == -1) { - return ending == '"' ? null : header.Substring(ap); + return ending == '"' ? null : header.Slice(ap).ToString(); } - return header.Substring(ap + 1, end - ap - 1); + return slice.Slice(0, end - ap - 1).ToString(); } private async Task LoadMultiPart(WebROCollection form) @@ -394,7 +395,7 @@ namespace Jellyfin.Server.SocketSharp } var elem = new Element(); - string header; + ReadOnlySpan<char> header; while ((header = ReadHeaders()) != null) { if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase)) @@ -404,7 +405,7 @@ namespace Jellyfin.Server.SocketSharp } else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase)) { - elem.ContentType = header.Substring("Content-Type:".Length).Trim(); + elem.ContentType = header.Slice("Content-Type:".Length).Trim().ToString(); elem.Encoding = GetEncoding(elem.ContentType); } } @@ -452,7 +453,7 @@ namespace Jellyfin.Server.SocketSharp return sb.ToString(); } - private static string GetContentDispositionAttribute(string l, string name) + private static string GetContentDispositionAttribute(ReadOnlySpan<char> l, string name) { int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); if (idx < 0) @@ -461,7 +462,7 @@ namespace Jellyfin.Server.SocketSharp } int begin = idx + name.Length + "=\"".Length; - int end = l.IndexOf('"', begin); + int end = l.Slice(begin).IndexOf('"'); if (end < 0) { return null; @@ -472,10 +473,10 @@ namespace Jellyfin.Server.SocketSharp return string.Empty; } - return l.Substring(begin, end - begin); + return l.Slice(begin, end - begin).ToString(); } - private string GetContentDispositionAttributeWithEncoding(string l, string name) + private string GetContentDispositionAttributeWithEncoding(ReadOnlySpan<char> l, string name) { int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); if (idx < 0) @@ -484,7 +485,7 @@ namespace Jellyfin.Server.SocketSharp } int begin = idx + name.Length + "=\"".Length; - int end = l.IndexOf('"', begin); + int end = l.Slice(begin).IndexOf('"'); if (end < 0) { return null; @@ -495,7 +496,7 @@ namespace Jellyfin.Server.SocketSharp return string.Empty; } - string temp = l.Substring(begin, end - begin); + ReadOnlySpan<char> temp = l.Slice(begin, end - begin); byte[] source = new byte[temp.Length]; for (int i = temp.Length - 1; i >= 0; i--) { diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 736f9feef..693c2328c 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -164,33 +164,19 @@ namespace Jellyfin.Server.SocketSharp Endpoint = endpoint }); - await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + await socket.StartReceive().ConfigureAwait(false); } } else { _logger.LogWarning("Web socket connection not allowed"); - ctx.Response.StatusCode = 401; - ctx.Response.Close(); + TryClose(ctx, 401); } } catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); - ctx.Response.StatusCode = 500; - ctx.Response.Close(); - } - } - - private async Task ReceiveWebSocketAsync(HttpListenerContext ctx, SharpWebSocket socket) - { - try - { - await socket.StartReceive().ConfigureAwait(false); - } - finally - { - TryClose(ctx, 200); + TryClose(ctx, 500); } } @@ -201,10 +187,6 @@ namespace Jellyfin.Server.SocketSharp ctx.Response.StatusCode = statusCode; ctx.Response.Close(); } - catch (ObjectDisposedException) - { - // TODO: Investigate and properly fix. - } catch (Exception ex) { _logger.LogError(ex, "Error closing web socket response"); diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index 6458707d9..069f47f9a 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -57,18 +57,37 @@ namespace Jellyfin.Server.SocketSharp public string XRealIp => string.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"]; private string remoteIp; - public string RemoteIp => - remoteIp ?? - (remoteIp = CheckBadChars(XForwardedFor) ?? - NormalizeIp(CheckBadChars(XRealIp) ?? - (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); + public string RemoteIp + { + get + { + if (remoteIp != null) + { + return remoteIp; + } + + var temp = CheckBadChars(XForwardedFor); + if (temp.Length != 0) + { + return remoteIp = temp.ToString(); + } + + temp = CheckBadChars(XRealIp); + if (temp.Length != 0) + { + return remoteIp = NormalizeIp(temp).ToString(); + } + + return remoteIp = NormalizeIp(request.RemoteEndPoint?.Address.ToString()).ToString(); + } + } private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; // CheckBadChars - throws on invalid chars to be not found in header name/value - internal static string CheckBadChars(string name) + internal static ReadOnlySpan<char> CheckBadChars(ReadOnlySpan<char> name) { - if (name == null || name.Length == 0) + if (name.Length == 0) { return name; } @@ -99,7 +118,7 @@ namespace Jellyfin.Server.SocketSharp } else if (c == 127 || (c < ' ' && c != '\t')) { - throw new ArgumentException("net_WebHeaderInvalidControlChars"); + throw new ArgumentException("net_WebHeaderInvalidControlChars", nameof(name)); } break; @@ -113,7 +132,7 @@ namespace Jellyfin.Server.SocketSharp break; } - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); } case 2: @@ -124,14 +143,14 @@ namespace Jellyfin.Server.SocketSharp break; } - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); } } } if (crlf != 0) { - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); } return name; @@ -150,16 +169,16 @@ namespace Jellyfin.Server.SocketSharp return false; } - private string NormalizeIp(string ip) + private ReadOnlySpan<char> NormalizeIp(ReadOnlySpan<char> ip) { - if (!string.IsNullOrWhiteSpace(ip)) + if (ip.Length != 0 && !ip.IsWhiteSpace()) { // Handle ipv4 mapped to ipv6 const string srch = "::ffff:"; var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase); if (index == 0) { - ip = ip.Substring(srch.Length); + ip = ip.Slice(srch.Length); } } @@ -302,17 +321,6 @@ namespace Jellyfin.Server.SocketSharp return null; } - public static string LeftPart(string strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle, StringComparison.Ordinal); - return pos == -1 ? strVal : strVal.Substring(0, pos); - } - public static ReadOnlySpan<char> LeftPart(ReadOnlySpan<char> strVal, char needle) { if (strVal == null) @@ -350,7 +358,7 @@ namespace Jellyfin.Server.SocketSharp } this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); - this.pathInfo = NormalizePathInfo(pathInfo, mode); + this.pathInfo = NormalizePathInfo(pathInfo, mode).ToString(); } return this.pathInfo; @@ -517,14 +525,14 @@ namespace Jellyfin.Server.SocketSharp } } - public static string NormalizePathInfo(string pathInfo, string handlerPath) + public static ReadOnlySpan<char> NormalizePathInfo(string pathInfo, string handlerPath) { if (handlerPath != null) { - var trimmed = pathInfo.TrimStart('/'); + var trimmed = pathInfo.AsSpan().TrimStart('/'); if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) { - return trimmed.Substring(handlerPath.Length); + return trimmed.Slice(handlerPath.Length).ToString(); } } diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index a037357ed..69673a49c 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -172,16 +172,9 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes)) { - if (string.IsNullOrEmpty(hasDtoOptions.EnableImageTypes)) - { - options.ImageTypes = Array.Empty<ImageType>(); - } - else - { - options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)) - .ToArray(); - } + options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)) + .ToArray(); } } diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 9caf07cea..201efe737 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -180,7 +181,7 @@ namespace MediaBrowser.Api return ToOptimizedResult(filters); } - private QueryFiltersLegacy GetFilters(BaseItem[] items) + private QueryFiltersLegacy GetFilters(IReadOnlyCollection<BaseItem> items) { var result = new QueryFiltersLegacy(); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 84475467f..3c7ad1d0a 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using MediaBrowser.Controller.Dto; @@ -197,29 +198,27 @@ namespace MediaBrowser.Api.UserLibrary request.ParentId = null; } - var item = string.IsNullOrEmpty(request.ParentId) ? - null : - _libraryManager.GetItemById(request.ParentId); + BaseItem item = null; - if (item == null) + if (!string.IsNullOrEmpty(request.ParentId)) { - item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder() : - _libraryManager.GetItemById(request.ParentId); + item = _libraryManager.GetItemById(request.ParentId); } - // Default list type = children + if (item == null) + { + item = _libraryManager.GetUserRootFolder(); + } - var folder = item as Folder; + Folder folder = item as Folder; if (folder == null) { - folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); + folder = _libraryManager.GetUserRootFolder(); } var hasCollectionType = folder as IHasCollectionType; - var isPlaylistQuery = (hasCollectionType != null && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)); - - if (isPlaylistQuery) + if (hasCollectionType != null + && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) { request.Recursive = true; request.IncludeItemTypes = "Playlist"; @@ -235,20 +234,12 @@ namespace MediaBrowser.Api.UserLibrary }; } - if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null) - { - return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); - } - - var userRoot = item as UserRootFolder; - - if (userRoot == null) + if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || !(item is UserRootFolder)) { return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); } var itemsArray = folder.GetChildren(user, true).ToArray(); - return new QueryResult<BaseItem> { Items = itemsArray, diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index aa99f6b58..cdaf95f5c 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -36,9 +36,7 @@ namespace MediaBrowser.Controller.Dto .ToArray(); public bool ContainsField(ItemFields field) - { - return AllItemFields.Contains(field); - } + => Fields.Contains(field); public DtoOptions(bool allFields) { @@ -47,15 +45,7 @@ namespace MediaBrowser.Controller.Dto EnableUserData = true; AddCurrentProgram = true; - if (allFields) - { - Fields = AllItemFields; - } - else - { - Fields = new ItemFields[] { }; - } - + Fields = allFields ? AllItemFields : Array.Empty<ItemFields>(); ImageTypes = AllImageTypes; } diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index df5ec5dd0..4b6fd58fe 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Dto /// <param name="options">The options.</param> /// <param name="user">The user.</param> /// <param name="owner">The owner.</param> - BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null); - - BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null); + BaseItemDto[] GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null); /// <summary> /// Gets the item by name dto. diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 8bfadbee6..e49ff20ba 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -810,37 +810,19 @@ namespace MediaBrowser.Controller.Entities { if (query.ItemIds.Length > 0) { - var result = LibraryManager.GetItemsResult(query); - - if (query.OrderBy.Length == 0) - { - var ids = query.ItemIds.ToList(); - - // Try to preserve order - result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id)).ToArray(); - } - return result; + return LibraryManager.GetItemsResult(query); } return GetItemsInternal(query); } - public BaseItem[] GetItemList(InternalItemsQuery query) + public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query) { query.EnableTotalRecordCount = false; if (query.ItemIds.Length > 0) { - var result = LibraryManager.GetItemList(query); - - if (query.OrderBy.Length == 0) - { - var ids = query.ItemIds.ToList(); - - // Try to preserve order - return result.OrderBy(i => ids.IndexOf(i.Id)).ToArray(); - } - return result.ToArray(); + return LibraryManager.GetItemList(query); } return GetItemsInternal(query).Items; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index f5f147db1..e378c2b89 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1904,7 +1904,7 @@ namespace MediaBrowser.Controller.MediaEncoding { flags.Add("+ignidx"); } - if (state.GenPtsInput) + if (state.GenPtsInput || string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { flags.Add("+genpts"); } diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index b812a8ddc..46593fb2f 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -32,16 +32,17 @@ namespace MediaBrowser.Controller.MediaEncoding var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); + // If ffmpeg process is closed, the state is disposed, so don't write to target in that case + if (!target.CanWrite) + { + break; + } + await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); await target.FlushAsync().ConfigureAwait(false); } } } - catch (ObjectDisposedException) - { - //TODO Investigate and properly fix. - // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux - } catch (Exception ex) { _logger.LogError(ex, "Error reading ffmpeg log"); diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web -Subproject b4842e325e9d7d708193b4a27060cfe4c978df5 +Subproject ec5a3b6e5efb6041153b92818aee562f20ee994 |
