diff options
37 files changed, 625 insertions, 715 deletions
diff --git a/.drone.yml b/.drone.yml index 972bf36f3..7705f4f93 100644 --- a/.drone.yml +++ b/.drone.yml @@ -90,22 +90,22 @@ steps: image: microsoft/dotnet:2-runtime err_ignore: true commands: - - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Jellyfin.Common.dll ci/ci-release/Jellyfin.Common.dll + - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Common.dll ci/ci-release/MediaBrowser.Common.dll - name: run-dotnet-compat-model image: microsoft/dotnet:2-runtime err_ignore: true commands: - - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Jellyfin.Model.dll ci/ci-release/Jellyfin.Model.dll + - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Model.dll ci/ci-release/MediaBrowser.Model.dll - name: run-dotnet-compat-controller image: microsoft/dotnet:2-runtime err_ignore: true commands: - - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Jellyfin.Controller.dll ci/ci-release/Jellyfin.Controller.dll + - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Controller.dll ci/ci-release/MediaBrowser.Controller.dll - name: run-dotnet-compat-naming image: microsoft/dotnet:2-runtime err_ignore: true commands: - - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Jellyfin.Naming.dll ci/ci-release/Jellyfin.Naming.dll + - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Emby.Naming.dll ci/ci-release/Emby.Naming.dll diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 037cdd8aa..b62c5e1d4 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; using Emby.Dlna.Common; using Emby.Dlna.Server; @@ -733,26 +734,21 @@ namespace Emby.Dlna.PlayTo return (true, null); } - XElement uPnpResponse; + XElement uPnpResponse = null; - // Handle different variations sent back by devices try { - uPnpResponse = XElement.Parse(trackString); + uPnpResponse = ParseResponse(trackString); } - catch (Exception) + catch (Exception ex) { - // first try to add a root node with a dlna namesapce - try - { - uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>"); - uPnpResponse = uPnpResponse.Descendants().First(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Unable to parse xml {0}", trackString); - return (true, null); - } + _logger.LogError(ex, "Uncaught exception while parsing xml"); + } + + if (uPnpResponse == null) + { + _logger.LogError("Failed to parse xml: \n {Xml}", trackString); + return (true, null); } var e = uPnpResponse.Element(uPnpNamespaces.items); @@ -762,6 +758,43 @@ namespace Emby.Dlna.PlayTo return (true, uTrack); } + private XElement ParseResponse(string xml) + { + // Handle different variations sent back by devices + try + { + return XElement.Parse(xml); + } + catch (XmlException) + { + + } + + // first try to add a root node with a dlna namesapce + try + { + return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>") + .Descendants() + .First(); + } + catch (XmlException) + { + + } + + // some devices send back invalid xml + try + { + return XElement.Parse(xml.Replace("&", "&")); + } + catch (XmlException) + { + + } + + return null; + } + private static uBaseObject CreateUBaseObject(XElement container, string trackUri) { if (container == null) diff --git a/Emby.Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs index 8cc14fa01..0f9fc08d9 100644 --- a/Emby.Notifications/CoreNotificationTypes.cs +++ b/Emby.Notifications/CoreNotificationTypes.cs @@ -11,101 +11,81 @@ namespace Emby.Notifications public class CoreNotificationTypes : INotificationTypeFactory { private readonly ILocalizationManager _localization; - private readonly IServerApplicationHost _appHost; - public CoreNotificationTypes(ILocalizationManager localization, IServerApplicationHost appHost) + public CoreNotificationTypes(ILocalizationManager localization) { _localization = localization; - _appHost = appHost; } public IEnumerable<NotificationTypeInfo> GetNotificationTypes() { - var knownTypes = new List<NotificationTypeInfo> + var knownTypes = new NotificationTypeInfo[] { new NotificationTypeInfo { Type = NotificationType.ApplicationUpdateInstalled.ToString() }, - new NotificationTypeInfo { Type = NotificationType.InstallationFailed.ToString() }, - new NotificationTypeInfo { Type = NotificationType.PluginInstalled.ToString() }, - new NotificationTypeInfo { Type = NotificationType.PluginError.ToString() }, - new NotificationTypeInfo { Type = NotificationType.PluginUninstalled.ToString() }, - new NotificationTypeInfo { Type = NotificationType.PluginUpdateInstalled.ToString() }, - new NotificationTypeInfo { Type = NotificationType.ServerRestartRequired.ToString() }, - new NotificationTypeInfo { Type = NotificationType.TaskFailed.ToString() }, - new NotificationTypeInfo { Type = NotificationType.NewLibraryContent.ToString() }, - new NotificationTypeInfo { Type = NotificationType.AudioPlayback.ToString() }, - new NotificationTypeInfo { Type = NotificationType.VideoPlayback.ToString() }, - new NotificationTypeInfo { Type = NotificationType.AudioPlaybackStopped.ToString() }, - new NotificationTypeInfo { Type = NotificationType.VideoPlaybackStopped.ToString() }, - new NotificationTypeInfo { Type = NotificationType.CameraImageUploaded.ToString() }, - new NotificationTypeInfo { Type = NotificationType.UserLockedOut.ToString() - } - }; - - if (!_appHost.CanSelfUpdate) - { - knownTypes.Add(new NotificationTypeInfo + }, + new NotificationTypeInfo { Type = NotificationType.ApplicationUpdateAvailable.ToString() - }); - } + } + }; foreach (var type in knownTypes) { diff --git a/Emby.Notifications/Notifications.cs b/Emby.Notifications/Notifications.cs index d3290479f..ec08fd193 100644 --- a/Emby.Notifications/Notifications.cs +++ b/Emby.Notifications/Notifications.cs @@ -5,21 +5,17 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Updates; using MediaBrowser.Controller; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Notifications; -using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Emby.Notifications @@ -29,43 +25,40 @@ namespace Emby.Notifications /// </summary> public class Notifications : IServerEntryPoint { - private readonly IInstallationManager _installationManager; - private readonly IUserManager _userManager; private readonly ILogger _logger; - private readonly ITaskManager _taskManager; private readonly INotificationManager _notificationManager; private readonly ILibraryManager _libraryManager; - private readonly ISessionManager _sessionManager; private readonly IServerApplicationHost _appHost; private Timer LibraryUpdateTimer { get; set; } private readonly object _libraryChangedSyncLock = new object(); private readonly IConfigurationManager _config; - private readonly IDeviceManager _deviceManager; private readonly ILocalizationManager _localization; private readonly IActivityManager _activityManager; private string[] _coreNotificationTypes; - public Notifications(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager) + public Notifications( + IActivityManager activityManager, + ILocalizationManager localization, + ILogger logger, + INotificationManager notificationManager, + ILibraryManager libraryManager, + IServerApplicationHost appHost, + IConfigurationManager config) { - _installationManager = installationManager; - _userManager = userManager; _logger = logger; - _taskManager = taskManager; _notificationManager = notificationManager; _libraryManager = libraryManager; - _sessionManager = sessionManager; _appHost = appHost; _config = config; - _deviceManager = deviceManager; _localization = localization; _activityManager = activityManager; - _coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray(); + _coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray(); } public Task RunAsync() @@ -124,10 +117,9 @@ namespace Emby.Notifications return _config.GetConfiguration<NotificationOptions>("notifications"); } - async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e) + private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e) { - // This notification is for users who can't auto-update (aka running as service) - if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate) + if (!_appHost.HasUpdateAvailable) { return; } @@ -145,7 +137,7 @@ namespace Emby.Notifications } private readonly List<BaseItem> _itemsAdded = new List<BaseItem>(); - void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) { if (!FilterItem(e.Item)) { diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index f26cc4f62..65cdccfa5 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -73,12 +73,6 @@ namespace Emby.Server.Implementations.AppBase public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations"); /// <summary> - /// Gets the path to where temporary update files will be stored - /// </summary> - /// <value>The plugin configurations path.</value> - public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates"); - - /// <summary> /// Gets the path to the log directory /// </summary> /// <value>The log directory path.</value> diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7d77500ab..8daba0585 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -123,12 +123,6 @@ namespace Emby.Server.Implementations /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value> public abstract bool CanSelfRestart { get; } - /// <summary> - /// Gets or sets a value indicating whether this instance can self update. - /// </summary> - /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value> - public virtual bool CanSelfUpdate => false; - public virtual bool CanLaunchWebBrowser { get @@ -1456,7 +1450,6 @@ namespace Emby.Server.Implementations OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName, CanSelfRestart = CanSelfRestart, - CanSelfUpdate = CanSelfUpdate, CanLaunchWebBrowser = CanLaunchWebBrowser, WanAddress = wanAddress, HasUpdateAvailable = HasUpdateAvailable, @@ -1756,21 +1749,6 @@ namespace Emby.Server.Implementations } /// <summary> - /// Updates the application. - /// </summary> - /// <param name="package">The package that contains the update</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress) - { - await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false); - - HasUpdateAvailable = false; - - OnApplicationUpdated(package); - } - - /// <summary> /// This returns localhost in the case of no external dns, and the hostname if the /// dns is prefixed with a valid Uri prefix. /// </summary> diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 0f432c36c..fba81306b 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -224,7 +224,7 @@ namespace Emby.Server.Implementations.Data }); } - db.ExecuteAll(string.Join(";", queries.ToArray())); + db.ExecuteAll(string.Join(";", queries)); Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First()); } @@ -232,23 +232,6 @@ namespace Emby.Server.Implementations.Data protected virtual int? CacheSize => null; - internal static void CheckOk(int rc) - { - string msg = ""; - - if (raw.SQLITE_OK != rc) - { - throw CreateException((ErrorCode)rc, msg); - } - } - - internal static Exception CreateException(ErrorCode rc, string msg) - { - var exp = new Exception(msg); - - return exp; - } - private bool _disposed; protected void CheckDisposed() { @@ -375,13 +358,6 @@ namespace Emby.Server.Implementations.Data } } - public class DummyToken : IDisposable - { - public void Dispose() - { - } - } - public static IDisposable Read(this ReaderWriterLockSlim obj) { //if (BaseSqliteRepository.ThreadSafeMode > 0) @@ -390,6 +366,7 @@ namespace Emby.Server.Implementations.Data //} return new WriteLockToken(obj); } + public static IDisposable Write(this ReaderWriterLockSlim obj) { //if (BaseSqliteRepository.ThreadSafeMode > 0) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 3014e482d..6502e4aed 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -536,7 +536,7 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(item)); } - SaveItems(new List<BaseItem> { item }, cancellationToken); + SaveItems(new [] { item }, cancellationToken); } public void SaveImages(BaseItem item) @@ -576,7 +576,7 @@ namespace Emby.Server.Implementations.Data /// or /// cancellationToken /// </exception> - public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken) + public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken) { if (items == null) { @@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.Data CheckDisposed(); - var tuples = new List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>>(); + var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>(); foreach (var item in items) { var ancestorIds = item.SupportsAncestors ? @@ -599,7 +599,7 @@ namespace Emby.Server.Implementations.Data var userdataKey = item.GetUserDataKeys().FirstOrDefault(); var inheritedTags = item.GetInheritedTags(); - tuples.Add(new Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>(item, ancestorIds, topParent, userdataKey, inheritedTags)); + tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags)); } using (WriteLock.Write()) @@ -615,7 +615,7 @@ namespace Emby.Server.Implementations.Data } } - private void SaveItemsInTranscation(IDatabaseConnection db, List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>> tuples) + private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples) { var statements = PrepareAllSafe(db, new string[] { @@ -966,7 +966,7 @@ namespace Emby.Server.Implementations.Data if (item.ExtraIds.Length > 0) { - saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray())); + saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds)); } else { @@ -1183,9 +1183,9 @@ namespace Emby.Server.Implementations.Data /// <exception cref="ArgumentException"></exception> public BaseItem RetrieveItem(Guid id) { - if (id.Equals(Guid.Empty)) + if (id == Guid.Empty) { - throw new ArgumentNullException(nameof(id)); + throw new ArgumentException(nameof(id), "Guid can't be empty"); } CheckDisposed(); @@ -2079,14 +2079,14 @@ namespace Emby.Server.Implementations.Data return false; } - var sortingFields = query.OrderBy.Select(i => i.Item1); + var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase); - return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase) - || sortingFields.Contains(ItemSortBy.SeriesDatePlayed, StringComparer.OrdinalIgnoreCase) + return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked) + || sortingFields.Contains(ItemSortBy.IsPlayed) + || sortingFields.Contains(ItemSortBy.IsUnplayed) + || sortingFields.Contains(ItemSortBy.PlayCount) + || sortingFields.Contains(ItemSortBy.DatePlayed) + || sortingFields.Contains(ItemSortBy.SeriesDatePlayed) || query.IsFavoriteOrLiked.HasValue || query.IsFavorite.HasValue || query.IsResumable.HasValue @@ -2094,9 +2094,9 @@ namespace Emby.Server.Implementations.Data || query.IsLiked.HasValue; } - private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields)) + private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields)) .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToList(); + .ToArray(); private string[] GetColumnNamesFromField(ItemFields field) { @@ -2151,18 +2151,26 @@ namespace Emby.Server.Implementations.Data } } - private bool HasProgramAttributes(InternalItemsQuery query) + private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; + "Series", + "Season", + "MusicAlbum", + "MusicArtist", + "PhotoAlbum" + }; + + private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + "Program", + "TvChannel", + "LiveTvProgram", + "LiveTvTvChannel" + }; - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + private bool HasProgramAttributes(InternalItemsQuery query) + { + if (_programExcludeParentTypes.Contains(query.ParentType)) { return false; } @@ -2172,29 +2180,18 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "Program", - "TvChannel", - "LiveTvProgram", - "LiveTvTvChannel" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _programTypes.Contains(x)); } - private bool HasServiceName(InternalItemsQuery query) + private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; + "TvChannel", + "LiveTvTvChannel" + }; - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + private bool HasServiceName(InternalItemsQuery query) + { + if (_programExcludeParentTypes.Contains(query.ParentType)) { return false; } @@ -2204,27 +2201,18 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "TvChannel", - "LiveTvTvChannel" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x)); } - private bool HasStartDate(InternalItemsQuery query) + private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; + "Program", + "LiveTvProgram" + }; - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + private bool HasStartDate(InternalItemsQuery query) + { + if (_programExcludeParentTypes.Contains(query.ParentType)) { return false; } @@ -2234,13 +2222,7 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "Program", - "LiveTvProgram" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x)); } private bool HasEpisodeAttributes(InternalItemsQuery query) @@ -2263,16 +2245,26 @@ namespace Emby.Server.Implementations.Data return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase); } - private bool HasArtistFields(InternalItemsQuery query) + + private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "PhotoAlbum" - }; + "Series", + "Season", + "PhotoAlbum" + }; + + private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + "Audio", + "MusicAlbum", + "MusicVideo", + "AudioBook", + "AudioPodcast" + }; - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + private bool HasArtistFields(InternalItemsQuery query) + { + if (_artistExcludeParentTypes.Contains(query.ParentType)) { return false; } @@ -2282,18 +2274,18 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "Audio", - "MusicAlbum", - "MusicVideo", - "AudioBook", - "AudioPodcast" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x)); } + private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + "Audio", + "MusicAlbum", + "MusicVideo", + "AudioBook", + "AudioPodcast" + }; + private bool HasSeriesFields(InternalItemsQuery query) { if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase)) @@ -2306,26 +2298,18 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new string[] - { - "Book", - "AudioBook", - "Episode", - "Season" - }; - - return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x)); } - private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns) + private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns) { var list = startColumns.ToList(); - foreach (var field in allFields) + foreach (var field in _allFields) { if (!HasField(query, field)) { - foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList()) + foreach (var fieldToRemove in GetColumnNamesFromField(field)) { list.Remove(fieldToRemove); } @@ -2419,11 +2403,14 @@ namespace Emby.Server.Implementations.Data list.Add(builder.ToString()); - var excludeIds = query.ExcludeItemIds.ToList(); - excludeIds.Add(item.Id); - excludeIds.AddRange(item.ExtraIds); + var oldLen = query.ExcludeItemIds.Length; + var newLen = oldLen + item.ExtraIds.Length + 1; + var excludeIds = new Guid[newLen]; + query.ExcludeItemIds.CopyTo(excludeIds, 0); + excludeIds[oldLen] = item.Id; + item.ExtraIds.CopyTo(excludeIds, oldLen + 1); - query.ExcludeItemIds = excludeIds.ToArray(); + query.ExcludeItemIds = excludeIds; query.ExcludeProviderIds = item.ProviderIds; } @@ -2444,7 +2431,7 @@ namespace Emby.Server.Implementations.Data list.Add(builder.ToString()); } - return list.ToArray(); + return list; } private void BindSearchParams(InternalItemsQuery query, IStatement statement) @@ -2723,18 +2710,17 @@ namespace Emby.Server.Implementations.Data private void AddItem(List<BaseItem> items, BaseItem newItem) { - var providerIds = newItem.ProviderIds.ToList(); - for (var i = 0; i < items.Count; i++) { var item = items[i]; - foreach (var providerId in providerIds) + foreach (var providerId in newItem.ProviderIds) { if (providerId.Key == MetadataProviders.TmdbCollection.ToString()) { continue; } + if (item.GetProviderId(providerId.Key) == providerId.Value) { if (newItem.SourceType == SourceType.Library) @@ -2753,10 +2739,10 @@ namespace Emby.Server.Implementations.Data { var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds; - int slowThreshold = 1000; + int slowThreshold = 100; #if DEBUG - slowThreshold = 250; + slowThreshold = 10; #endif if (elapsed >= slowThreshold) @@ -2806,7 +2792,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses); commandText += whereText + GetGroupBy(query) @@ -2930,25 +2916,31 @@ namespace Emby.Server.Implementations.Data private string GetOrderByText(InternalItemsQuery query) { - var orderBy = query.OrderBy.ToList(); - var enableOrderInversion = false; - - if (query.SimilarTo != null && orderBy.Count == 0) + if (string.IsNullOrEmpty(query.SearchTerm)) { - orderBy.Add(new ValueTuple<string, SortOrder>("SimilarityScore", SortOrder.Descending)); - orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)); - } + int oldLen = query.OrderBy.Length; - if (!string.IsNullOrEmpty(query.SearchTerm)) + if (query.SimilarTo != null && oldLen == 0) + { + var arr = new (string, SortOrder)[oldLen + 2]; + query.OrderBy.CopyTo(arr, 0); + arr[oldLen] = ("SimilarityScore", SortOrder.Descending); + arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending); + query.OrderBy = arr; + } + } + else { - orderBy = new List<(string, SortOrder)>(); - orderBy.Add(new ValueTuple<string, SortOrder>("SearchScore", SortOrder.Descending)); - orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending)); + query.OrderBy = new [] + { + ("SearchScore", SortOrder.Descending), + (ItemSortBy.SortName, SortOrder.Ascending) + }; } - query.OrderBy = orderBy.ToArray(); + var orderBy = query.OrderBy; - if (orderBy.Count == 0) + if (orderBy.Length == 0) { return string.Empty; } @@ -2957,6 +2949,7 @@ namespace Emby.Server.Implementations.Data { var columnMap = MapOrderByField(i.Item1, query); var columnAscending = i.Item2 == SortOrder.Ascending; + const bool enableOrderInversion = false; if (columnMap.Item2 && enableOrderInversion) { columnAscending = !columnAscending; @@ -2968,7 +2961,7 @@ namespace Emby.Server.Implementations.Data })); } - private ValueTuple<string, bool> MapOrderByField(string name, InternalItemsQuery query) + private (string, bool) MapOrderByField(string name, InternalItemsQuery query) { if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase)) { @@ -3218,7 +3211,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses); commandText += whereText + GetGroupBy(query) @@ -4378,7 +4371,7 @@ namespace Emby.Server.Implementations.Data } else if (query.Years.Length > 1) { - var val = string.Join(",", query.Years.ToArray()); + var val = string.Join(",", query.Years); whereClauses.Add("ProductionYear in (" + val + ")"); } @@ -4952,7 +4945,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return result; } - return new[] { value }.Where(IsValidType); + if (IsValidType(value)) + { + return new[] { value }; + } + + return Array.Empty<string>(); } public void DeleteItem(Guid id, CancellationToken cancellationToken) @@ -5215,32 +5213,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) { return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); } @@ -5317,7 +5315,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) + private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) { if (query == null) { @@ -5335,7 +5333,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var typeClause = itemValueTypes.Length == 1 ? ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : - ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")"); + ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")"); InternalItemsQuery typeSubQuery = null; @@ -5363,11 +5361,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")"); - var typeWhereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses); - - itemCountColumnQuery += typeWhereText; + itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses); itemCountColumns = new Dictionary<string, string>() { @@ -5400,7 +5394,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type IsSeries = query.IsSeries }; - columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList(); + columns = GetFinalColumnsToSelect(query, columns); var commandText = "select " + string.Join(",", columns) @@ -5492,8 +5486,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { return connection.RunInTransaction(db => { - var list = new List<Tuple<BaseItem, ItemCounts>>(); - var result = new QueryResult<Tuple<BaseItem, ItemCounts>>(); + var list = new List<(BaseItem, ItemCounts)>(); + var result = new QueryResult<(BaseItem, ItemCounts)>(); var statements = PrepareAllSafe(db, statementTexts); @@ -5531,7 +5525,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var countStartColumn = columns.Count - 1; - list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount))); + list.Add((item, GetItemCounts(row, countStartColumn, typesToCount))); } } @@ -6198,6 +6192,5 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return item; } - } } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 75ca57ebb..070717d48 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null) { - var result = new StreamWriter(content, contentType, _logger); + var result = new StreamWriter(content, contentType); if (responseHeaders == null) { @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer content = Array.Empty<byte>(); } - result = new StreamWriter(content, contentType, contentLength, _logger); + result = new StreamWriter(content, contentType, contentLength); } else { @@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary<string, string>(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) + if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) { responseHeaders["Expires"] = "-1"; } @@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer bytes = Array.Empty<byte>(); } - result = new StreamWriter(bytes, contentType, contentLength, _logger); + result = new StreamWriter(bytes, contentType, contentLength); } else { @@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary<string, string>(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) + if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) { responseHeaders["Expires"] = "-1"; } @@ -277,9 +277,10 @@ namespace Emby.Server.Implementations.HttpServer private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null) { - var contentType = request.ResponseContentType; + // TODO: @bond use Span and .Equals + var contentType = request.ResponseContentType?.Split(';')[0].Trim().ToLowerInvariant(); - switch (GetRealContentType(contentType)) + switch (contentType) { case "application/xml": case "text/xml": @@ -333,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger); + var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength); AddResponseHeaders(result, responseHeaders); return result; } else { - var result = new StreamWriter(content, contentType, contentLength, _logger); + var result = new StreamWriter(content, contentType, contentLength); AddResponseHeaders(result, responseHeaders); return result; } @@ -348,13 +349,19 @@ namespace Emby.Server.Implementations.HttpServer private byte[] Compress(byte[] bytes, string compressionType) { if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase)) + { return CompressBrotli(bytes); + } if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase)) + { return Deflate(bytes); + } if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase)) + { return GZip(bytes); + } throw new NotSupportedException(compressionType); } @@ -390,13 +397,6 @@ namespace Emby.Server.Implementations.HttpServer } } - public static string GetRealContentType(string contentType) - { - return contentType == null - ? null - : contentType.Split(';')[0].ToLowerInvariant().Trim(); - } - private static string SerializeToXmlString(object from) { using (var ms = new MemoryStream()) @@ -603,7 +603,7 @@ namespace Emby.Server.Implementations.HttpServer } } - var hasHeaders = new StreamWriter(stream, contentType, _logger) + var hasHeaders = new StreamWriter(stream, contentType) { OnComplete = options.OnComplete, OnError = options.OnError diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 3269d44cf..cb2e3580b 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -14,8 +14,6 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> public class StreamWriter : IAsyncStreamWriter, IHasHeaders { - private ILogger Logger { get; set; } - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// <summary> @@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.HttpServer /// <param name="source">The source.</param> /// <param name="contentType">Type of the content.</param> /// <param name="logger">The logger.</param> - public StreamWriter(Stream source, string contentType, ILogger logger) + public StreamWriter(Stream source, string contentType) { if (string.IsNullOrEmpty(contentType)) { @@ -53,7 +51,6 @@ namespace Emby.Server.Implementations.HttpServer } SourceStream = source; - Logger = logger; Headers["Content-Type"] = contentType; @@ -69,7 +66,7 @@ namespace Emby.Server.Implementations.HttpServer /// <param name="source">The source.</param> /// <param name="contentType">Type of the content.</param> /// <param name="logger">The logger.</param> - public StreamWriter(byte[] source, string contentType, int contentLength, ILogger logger) + public StreamWriter(byte[] source, string contentType, int contentLength) { if (string.IsNullOrEmpty(contentType)) { @@ -77,7 +74,6 @@ namespace Emby.Server.Implementations.HttpServer } SourceBytes = source; - Logger = logger; Headers["Content-Type"] = contentType; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 87809cae8..3c2272b56 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1225,9 +1225,9 @@ namespace Emby.Server.Implementations.Library /// <exception cref="ArgumentNullException">id</exception> public BaseItem GetItemById(Guid id) { - if (id.Equals(Guid.Empty)) + if (id == Guid.Empty) { - throw new ArgumentNullException(nameof(id)); + throw new ArgumentException(nameof(id), "Guid can't be empty"); } if (LibraryItemsCache.TryGetValue(id, out BaseItem item)) @@ -1237,8 +1237,6 @@ namespace Emby.Server.Implementations.Library item = RetrieveItem(id); - //_logger.LogDebug("GetitemById {0}", id); - if (item != null) { RegisterItem(item); @@ -1333,7 +1331,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetItemIdsList(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) { if (query.User != null) { @@ -1344,7 +1342,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetStudios(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) { if (query.User != null) { @@ -1355,7 +1353,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetGenres(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query) { if (query.User != null) { @@ -1366,7 +1364,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetMusicGenres(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1377,7 +1375,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetAllArtists(query); } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1421,7 +1419,7 @@ namespace Emby.Server.Implementations.Library } } - public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1808,18 +1806,16 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken) { - var list = items.ToList(); - - ItemRepository.SaveItems(list, cancellationToken); + ItemRepository.SaveItems(items, cancellationToken); - foreach (var item in list) + foreach (var item in items) { RegisterItem(item); } if (ItemAdded != null) { - foreach (var item in list) + foreach (var item in items) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -1853,7 +1849,7 @@ namespace Emby.Server.Implementations.Library /// <summary> /// Updates the item. /// </summary> - public void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { foreach (var item in items) { @@ -1908,7 +1904,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - UpdateItems(new List<BaseItem> { item }, parent, updateReason, cancellationToken); + UpdateItems(new [] { item }, parent, updateReason, cancellationToken); } /// <summary> @@ -2005,9 +2001,7 @@ namespace Emby.Server.Implementations.Library .FirstOrDefault(); } - var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); - - return options; + return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); } public string GetContentType(BaseItem item) @@ -2017,11 +2011,13 @@ namespace Emby.Server.Implementations.Library { return configuredContentType; } + configuredContentType = GetConfiguredContentType(item, true); if (!string.IsNullOrEmpty(configuredContentType)) { return configuredContentType; } + return GetInheritedContentType(item); } @@ -2056,6 +2052,7 @@ namespace Emby.Server.Implementations.Library { return collectionFolder.CollectionType; } + return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath); } @@ -2066,6 +2063,7 @@ namespace Emby.Server.Implementations.Library { return nameValuePair.Value; } + return null; } @@ -2108,9 +2106,9 @@ namespace Emby.Server.Implementations.Library string viewType, string sortName) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views"); - - path = Path.Combine(path, _fileSystem.GetValidFilename(viewType)); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, + "views", + _fileSystem.GetValidFilename(viewType)); var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView)); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 15cbbdb5a..dfef8e997 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -168,9 +168,9 @@ namespace Emby.Server.Implementations.Library /// <exception cref="ArgumentNullException"></exception> public User GetUserById(Guid id) { - if (id.Equals(Guid.Empty)) + if (id == Guid.Empty) { - throw new ArgumentNullException(nameof(id)); + throw new ArgumentException(nameof(id), "Guid can't be empty"); } return Users.FirstOrDefault(u => u.Id == id); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index a36302876..f7ef16fb0 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.LiveTv public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { - var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId); + var user = query.UserId == Guid.Empty ? null : _userManager.GetUserById(query.UserId); var topFolder = GetInternalLiveTvFolder(cancellationToken); diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index 44898d498..8ae7fd90c 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -42,6 +42,27 @@ namespace Emby.Server.Implementations.Serialization } /// <summary> + /// Serializes to stream. + /// </summary> + /// <param name="obj">The obj.</param> + /// <param name="stream">The stream.</param> + /// <exception cref="ArgumentNullException">obj</exception> + public void SerializeToStream<T>(T obj, Stream stream) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + ServiceStack.Text.JsonSerializer.SerializeToStream<T>(obj, stream); + } + + /// <summary> /// Serializes to file. /// </summary> /// <param name="obj">The obj.</param> diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 2a2f1dde6..41ee73a56 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -187,26 +187,13 @@ namespace Jellyfin.Server if (string.IsNullOrEmpty(dataDir)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - } - else - { - // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. - dataDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); - - // If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. - if (string.IsNullOrEmpty(dataDir)) - { - dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share"); - } - } - - dataDir = Path.Combine(dataDir, "jellyfin"); + // LocalApplicationData follows the XDG spec on unix machines + dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "jellyfin"); } } + Directory.CreateDirectory(dataDir); + // configDir // IF --configdir // ELSE IF $JELLYFIN_CONFIG_DIR @@ -216,7 +203,6 @@ namespace Jellyfin.Server // ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin // ELSE $HOME/.config/jellyfin var configDir = options.ConfigDir; - if (string.IsNullOrEmpty(configDir)) { configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR"); @@ -300,7 +286,6 @@ namespace Jellyfin.Server // Ensure the main folders exist before we continue try { - Directory.CreateDirectory(dataDir); Directory.CreateDirectory(logDir); Directory.CreateDirectory(configDir); Directory.CreateDirectory(cacheDir); diff --git a/Jellyfin.Server/SocketSharp/HttpFile.cs b/Jellyfin.Server/SocketSharp/HttpFile.cs index 89c75e536..448b666b6 100644 --- a/Jellyfin.Server/SocketSharp/HttpFile.cs +++ b/Jellyfin.Server/SocketSharp/HttpFile.cs @@ -6,9 +6,13 @@ namespace Jellyfin.Server.SocketSharp public class HttpFile : IHttpFile { public string Name { get; set; } + public string FileName { get; set; } + public long ContentLength { get; set; } + public string ContentType { get; set; } + public Stream InputStream { get; set; } } } diff --git a/Jellyfin.Server/SocketSharp/HttpPostedFile.cs b/Jellyfin.Server/SocketSharp/HttpPostedFile.cs new file mode 100644 index 000000000..f38ed848e --- /dev/null +++ b/Jellyfin.Server/SocketSharp/HttpPostedFile.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +public sealed class HttpPostedFile : IDisposable +{ + private string _name; + private string _contentType; + private Stream _stream; + private bool _disposed = false; + + internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) + { + _name = name; + _contentType = content_type; + _stream = new ReadSubStream(base_stream, offset, length); + } + + public string ContentType => _contentType; + + public int ContentLength => (int)_stream.Length; + + public string FileName => _name; + + public Stream InputStream => _stream; + + /// <summary> + /// Releases the unmanaged resources and disposes of the managed resources used. + /// </summary> + public void Dispose() + { + if (_disposed) + { + return; + } + + _stream.Dispose(); + _stream = null; + + _name = null; + _contentType = null; + + _disposed = true; + } + + private class ReadSubStream : Stream + { + private Stream _stream; + private long _offset; + private long _end; + private long _position; + + public ReadSubStream(Stream s, long offset, long length) + { + _stream = s; + _offset = offset; + _end = offset + length; + _position = offset; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int dest_offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (dest_offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "< 0"); + } + + int len = buffer.Length; + if (dest_offset > len) + { + throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); + } + + // reordered to avoid possible integer overflow + if (dest_offset > len - count) + { + throw new ArgumentException("Reading would overrun buffer", nameof(count)); + } + + if (count > _end - _position) + { + count = (int)(_end - _position); + } + + if (count <= 0) + { + return 0; + } + + _stream.Position = _position; + int result = _stream.Read(buffer, dest_offset, count); + if (result > 0) + { + _position += result; + } + else + { + _position = _end; + } + + return result; + } + + public override int ReadByte() + { + if (_position >= _end) + { + return -1; + } + + _stream.Position = _position; + int result = _stream.ReadByte(); + if (result < 0) + { + _position = _end; + } + else + { + _position++; + } + + return result; + } + + public override long Seek(long d, SeekOrigin origin) + { + long real; + switch (origin) + { + case SeekOrigin.Begin: + real = _offset + d; + break; + case SeekOrigin.End: + real = _end + d; + break; + case SeekOrigin.Current: + real = _position + d; + break; + default: + throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); + } + + long virt = real - _offset; + if (virt < 0 || virt > Length) + { + throw new ArgumentException("Invalid position", nameof(d)); + } + + _position = _stream.Seek(real, SeekOrigin.Begin); + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => _end - _offset; + + public override long Position + { + get => _position - _offset; + set + { + if (value > Length) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _position = Seek(value, SeekOrigin.Begin); + } + } + } +} diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index 24cf994ea..f2a08c9ae 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -225,7 +225,7 @@ namespace Jellyfin.Server.SocketSharp if (starts_with) { - return StrUtils.StartsWith(ContentType, ct, true); + return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); } return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); @@ -324,215 +324,6 @@ namespace Jellyfin.Server.SocketSharp return result.ToString(); } } - - public sealed class HttpPostedFile - { - private string name; - private string content_type; - private Stream stream; - - private class ReadSubStream : Stream - { - private Stream s; - private long offset; - private long end; - private long position; - - public ReadSubStream(Stream s, long offset, long length) - { - this.s = s; - this.offset = offset; - this.end = offset + length; - position = offset; - } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int dest_offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (dest_offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "< 0"); - } - - int len = buffer.Length; - if (dest_offset > len) - { - throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); - } - - // reordered to avoid possible integer overflow - if (dest_offset > len - count) - { - throw new ArgumentException("Reading would overrun buffer", nameof(count)); - } - - if (count > end - position) - { - count = (int)(end - position); - } - - if (count <= 0) - { - return 0; - } - - s.Position = position; - int result = s.Read(buffer, dest_offset, count); - if (result > 0) - { - position += result; - } - else - { - position = end; - } - - return result; - } - - public override int ReadByte() - { - if (position >= end) - { - return -1; - } - - s.Position = position; - int result = s.ReadByte(); - if (result < 0) - { - position = end; - } - else - { - position++; - } - - return result; - } - - public override long Seek(long d, SeekOrigin origin) - { - long real; - switch (origin) - { - case SeekOrigin.Begin: - real = offset + d; - break; - case SeekOrigin.End: - real = end + d; - break; - case SeekOrigin.Current: - real = position + d; - break; - default: - throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); - } - - long virt = real - offset; - if (virt < 0 || virt > Length) - { - throw new ArgumentException("Invalid position", nameof(d)); - } - - position = s.Seek(real, SeekOrigin.Begin); - return position; - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => end - offset; - - public override long Position - { - get => position - offset; - set - { - if (value > Length) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - position = Seek(value, SeekOrigin.Begin); - } - } - } - - internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) - { - this.name = name; - this.content_type = content_type; - this.stream = new ReadSubStream(base_stream, offset, length); - } - - public string ContentType => content_type; - - public int ContentLength => (int)stream.Length; - - public string FileName => name; - - public Stream InputStream => stream; - } - - internal static class StrUtils - { - public static bool StartsWith(string str1, string str2, bool ignore_case) - { - if (string.IsNullOrEmpty(str1)) - { - return false; - } - - var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - return str1.IndexOf(str2, comparison) == 0; - } - - public static bool EndsWith(string str1, string str2, bool ignore_case) - { - int l2 = str2.Length; - if (l2 == 0) - { - return true; - } - - int l1 = str1.Length; - if (l2 > l1) - { - return false; - } - - var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1; - } - } - private class HttpMultipart { @@ -606,12 +397,12 @@ namespace Jellyfin.Server.SocketSharp string header; while ((header = ReadHeaders()) != null) { - if (StrUtils.StartsWith(header, "Content-Disposition:", true)) + if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase)) { elem.Name = GetContentDispositionAttribute(header, "name"); elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); } - else if (StrUtils.StartsWith(header, "Content-Type:", true)) + else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase)) { elem.ContentType = header.Substring("Content-Type:".Length).Trim(); elem.Encoding = GetEncoding(elem.ContentType); @@ -730,13 +521,14 @@ namespace Jellyfin.Server.SocketSharp return false; } - if (!StrUtils.EndsWith(line, boundary, false)) + if (!line.EndsWith(boundary, StringComparison.Ordinal)) { return true; } } catch { + } return false; diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index cbce7d457..6458707d9 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -28,30 +28,6 @@ namespace Jellyfin.Server.SocketSharp // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); } - private static string GetHandlerPathIfAny(string listenerUrl) - { - if (listenerUrl == null) - { - return null; - } - - var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); - if (pos == -1) - { - return null; - } - - var startHostUrl = listenerUrl.Substring(pos + "://".Length); - var endPos = startHostUrl.IndexOf('/', StringComparison.Ordinal); - if (endPos == -1) - { - return null; - } - - var endHostUrl = startHostUrl.Substring(endPos + 1); - return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); - } - public HttpListenerRequest HttpRequest => request; public object OriginalRequest => request; @@ -102,7 +78,7 @@ namespace Jellyfin.Server.SocketSharp name = name.Trim(HttpTrimCharacters); // First, check for correctly formed multi-line value - // Second, check for absenece of CTL characters + // Second, check for absence of CTL characters int crlf = 0; for (int i = 0; i < name.Length; ++i) { @@ -231,8 +207,15 @@ namespace Jellyfin.Server.SocketSharp { foreach (var acceptsType in acceptContentTypes) { - var contentType = HttpResultFactory.GetRealContentType(acceptsType); - acceptsAnything = acceptsAnything || contentType == "*/*"; + // TODO: @bond move to Span when Span.Split lands + // https://github.com/dotnet/corefx/issues/26528 + var contentType = acceptsType?.Split(';')[0].Trim(); + acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase); + + if (acceptsAnything) + { + break; + } } if (acceptsAnything) @@ -241,7 +224,7 @@ namespace Jellyfin.Server.SocketSharp { return defaultContentType; } - else if (serverDefaultContentType != null) + else { return serverDefaultContentType; } @@ -284,11 +267,11 @@ namespace Jellyfin.Server.SocketSharp private static string GetQueryStringContentType(IRequest httpReq) { - var format = httpReq.QueryString["format"]; + ReadOnlySpan<char> format = httpReq.QueryString["format"]; if (format == null) { const int formatMaxLength = 4; - var pi = httpReq.PathInfo; + ReadOnlySpan<char> pi = httpReq.PathInfo; if (pi == null || pi.Length <= formatMaxLength) { return null; @@ -296,7 +279,7 @@ namespace Jellyfin.Server.SocketSharp if (pi[0] == '/') { - pi = pi.Substring(1); + pi = pi.Slice(1); } format = LeftPart(pi, '/'); @@ -330,6 +313,17 @@ namespace Jellyfin.Server.SocketSharp return pos == -1 ? strVal : strVal.Substring(0, pos); } + public static ReadOnlySpan<char> LeftPart(ReadOnlySpan<char> strVal, char needle) + { + if (strVal == null) + { + return null; + } + + var pos = strVal.IndexOf(needle); + return pos == -1 ? strVal : strVal.Slice(0, pos); + } + public static string HandlerFactoryPath; private string pathInfo; @@ -341,7 +335,7 @@ namespace Jellyfin.Server.SocketSharp { var mode = HandlerFactoryPath; - var pos = request.RawUrl.IndexOf("?", StringComparison.Ordinal); + var pos = request.RawUrl.IndexOf('?', StringComparison.Ordinal); if (pos != -1) { var path = request.RawUrl.Substring(0, pos); @@ -525,10 +519,13 @@ namespace Jellyfin.Server.SocketSharp public static string NormalizePathInfo(string pathInfo, string handlerPath) { - var trimmed = pathInfo.TrimStart('/'); - if (handlerPath != null && trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) + if (handlerPath != null) { - return trimmed.Substring(handlerPath.Length); + var trimmed = pathInfo.TrimStart('/'); + if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) + { + return trimmed.Substring(handlerPath.Length); + } } return pathInfo; diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index 56e5c73d6..cf5aee5d4 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -55,6 +55,41 @@ namespace Jellyfin.Server.SocketSharp public QueryParamCollection Headers => _response.Headers; + private static string AsHeaderValue(Cookie cookie) + { + DateTime defaultExpires = DateTime.MinValue; + + var path = cookie.Expires == defaultExpires + ? "/" + : cookie.Path ?? "/"; + + var sb = new StringBuilder(); + + sb.Append($"{cookie.Name}={cookie.Value};path={path}"); + + if (cookie.Expires != defaultExpires) + { + sb.Append($";expires={cookie.Expires:R}"); + } + + if (!string.IsNullOrEmpty(cookie.Domain)) + { + sb.Append($";domain={cookie.Domain}"); + } + + if (cookie.Secure) + { + sb.Append(";Secure"); + } + + if (cookie.HttpOnly) + { + sb.Append(";HttpOnly"); + } + + return sb.ToString(); + } + public void AddHeader(string name, string value) { if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) @@ -126,41 +161,6 @@ namespace Jellyfin.Server.SocketSharp _response.Headers.Add("Set-Cookie", cookieStr); } - public static string AsHeaderValue(Cookie cookie) - { - var defaultExpires = DateTime.MinValue; - - var path = cookie.Expires == defaultExpires - ? "/" - : cookie.Path ?? "/"; - - var sb = new StringBuilder(); - - sb.Append($"{cookie.Name}={cookie.Value};path={path}"); - - if (cookie.Expires != defaultExpires) - { - sb.Append($";expires={cookie.Expires:R}"); - } - - if (!string.IsNullOrEmpty(cookie.Domain)) - { - sb.Append($";domain={cookie.Domain}"); - } - - if (cookie.Secure) - { - sb.Append(";Secure"); - } - - if (cookie.HttpOnly) - { - sb.Append(";HttpOnly"); - } - - return sb.ToString(); - } - public bool SendChunked { get => _response.SendChunked; diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 451ee72dd..a037357ed 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api @@ -118,8 +119,7 @@ namespace MediaBrowser.Api { var options = new DtoOptions(); - var hasFields = request as IHasItemFields; - if (hasFields != null) + if (request is IHasItemFields hasFields) { options.Fields = hasFields.GetItemFields(); } @@ -133,9 +133,11 @@ namespace MediaBrowser.Api client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1) { - var list = options.Fields.ToList(); - list.Add(Model.Querying.ItemFields.RecursiveItemCount); - options.Fields = list.ToArray(); + int oldLen = options.Fields.Length; + var arr = new ItemFields[oldLen + 1]; + options.Fields.CopyTo(arr, 0); + arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount; + options.Fields = arr; } if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || @@ -146,9 +148,12 @@ namespace MediaBrowser.Api client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 || client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1) { - var list = options.Fields.ToList(); - list.Add(Model.Querying.ItemFields.ChildCount); - options.Fields = list.ToArray(); + + int oldLen = options.Fields.Length; + var arr = new ItemFields[oldLen + 1]; + options.Fields.CopyTo(arr, 0); + arr[oldLen] = Model.Querying.ItemFields.ChildCount; + options.Fields = arr; } } @@ -167,7 +172,16 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes)) { - options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray(); + 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(); + } } } diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index 16b036912..b7e94b73f 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -197,16 +197,6 @@ namespace MediaBrowser.Api.ScheduledTasks throw new ResourceNotFoundException("Task not found"); } - if (string.Equals(task.ScheduledTask.Key, "SystemUpdateTask", StringComparison.OrdinalIgnoreCase)) - { - // This is a hack for now just to get the update application function to work when auto-update is disabled - if (!_config.Configuration.EnableAutoUpdate) - { - _config.Configuration.EnableAutoUpdate = true; - _config.SaveConfiguration(); - } - } - TaskManager.Execute(task, new TaskOptions()); } @@ -238,16 +228,14 @@ namespace MediaBrowser.Api.ScheduledTasks // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs var id = GetPathValue(1); - var task = TaskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id)); + var task = TaskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.Ordinal)); if (task == null) { throw new ResourceNotFoundException("Task not found"); } - var triggerInfos = request; - - task.Triggers = triggerInfos.ToArray(); + task.Triggers = request.ToArray(); } } } diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 651da1939..7a8455ff2 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -112,7 +112,7 @@ namespace MediaBrowser.Api.UserLibrary return ToOptimizedResult(result); } - protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) + protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) { if (request is GetAlbumArtists) { diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 471b41127..e3c9ae58e 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -209,9 +209,9 @@ namespace MediaBrowser.Api.UserLibrary }; } - protected virtual QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) + protected virtual QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) { - return new QueryResult<Tuple<BaseItem, ItemCounts>>(); + return new QueryResult<(BaseItem, ItemCounts)>(); } private void SetItemCounts(BaseItemDto dto, ItemCounts counts) diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 7af50c329..a26f59573 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -396,14 +396,12 @@ namespace MediaBrowser.Api.UserLibrary public VideoType[] GetVideoTypes() { - var val = VideoTypes; - - if (string.IsNullOrEmpty(val)) + if (string.IsNullOrEmpty(VideoTypes)) { - return new VideoType[] { }; + return Array.Empty<VideoType>(); } - return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray(); + return VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray(); } /// <summary> diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index baf570d50..0c04d02dd 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Api.UserLibrary return ToOptimizedResult(result); } - protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) + protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) { var viewType = GetParentItemViewType(request); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 3ae7da007..96b0aa003 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -90,7 +90,7 @@ namespace MediaBrowser.Api.UserLibrary var options = GetDtoOptions(_authContext, request); - var ancestorIds = new List<Guid>(); + var ancestorIds = Array.Empty<Guid>(); var excludeFolderIds = user.Configuration.LatestItemsExcludes; if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) @@ -99,12 +99,12 @@ namespace MediaBrowser.Api.UserLibrary .Where(i => i is Folder) .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N"))) .Select(i => i.Id) - .ToList(); + .ToArray(); } var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) { - OrderBy = new[] { ItemSortBy.DatePlayed }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }, IsResumable = true, StartIndex = request.StartIndex, Limit = request.Limit, @@ -115,7 +115,7 @@ namespace MediaBrowser.Api.UserLibrary IsVirtualItem = false, CollapseBoxSetItems = false, EnableTotalRecordCount = request.EnableTotalRecordCount, - AncestorIds = ancestorIds.ToArray(), + AncestorIds = ancestorIds, IncludeItemTypes = request.GetIncludeItemTypes(), ExcludeItemTypes = request.GetExcludeItemTypes(), SearchTerm = request.SearchTerm @@ -155,7 +155,7 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> private QueryResult<BaseItemDto> GetItems(GetItems request) { - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; + var user = request.UserId == Guid.Empty ? null : _userManager.GetUserById(request.UserId); var dtoOptions = GetDtoOptions(_authContext, request); @@ -190,11 +190,8 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> private QueryResult<BaseItem> GetQueryResult(GetItems request, DtoOptions dtoOptions, User user) { - if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)) - { - request.ParentId = null; - } - else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) + || string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) { request.ParentId = null; } diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs index 4fcc3aa53..94f5262b0 100644 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Api.UserLibrary return ToOptimizedResult(result); } - protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) + protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) { return LibraryManager.GetMusicGenres(query); } diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index d317f9f38..c26457778 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary throw new NotImplementedException(); } - protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) + protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) { var items = LibraryManager.GetPeopleItems(new InternalPeopleQuery { @@ -109,10 +109,10 @@ namespace MediaBrowser.Api.UserLibrary NameContains = query.NameContains ?? query.SearchTerm }); - return new QueryResult<Tuple<BaseItem, ItemCounts>> + return new QueryResult<(BaseItem, ItemCounts)> { TotalRecordCount = items.Count, - Items = items.Take(query.Limit ?? int.MaxValue).Select(i => new Tuple<BaseItem, ItemCounts>(i, new ItemCounts())).ToArray() + Items = items.Take(query.Limit ?? int.MaxValue).Select(i => (i as BaseItem, new ItemCounts())).ToArray() }; } diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index 4e2483a56..890acc931 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.Api.UserLibrary return ToOptimizedResult(result); } - protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) + protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) { return LibraryManager.GetStudios(query); } diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index 27092c0e1..cb4e8bf5f 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -42,12 +42,6 @@ namespace MediaBrowser.Common.Configuration string PluginConfigurationsPath { get; } /// <summary> - /// Gets the path to where temporary update files will be stored - /// </summary> - /// <value>The plugin configurations path.</value> - string TempUpdatePath { get; } - - /// <summary> /// Gets the path to the log directory /// </summary> /// <value>The log directory path.</value> diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 6891152ee..3a4098612 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -73,12 +73,6 @@ namespace MediaBrowser.Common string ApplicationUserAgent { get; } /// <summary> - /// Gets or sets a value indicating whether this instance can self update. - /// </summary> - /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value> - bool CanSelfUpdate { get; } - - /// <summary> /// Gets the exports. /// </summary> /// <typeparam name="T"></typeparam> @@ -87,12 +81,6 @@ namespace MediaBrowser.Common IEnumerable<T> GetExports<T>(bool manageLifetime = true); /// <summary> - /// Updates the application. - /// </summary> - /// <returns>Task.</returns> - Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress); - - /// <summary> /// Resolves this instance. /// </summary> /// <typeparam name="T"></typeparam> diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 60c183d04..511356aa4 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -193,7 +193,7 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Updates the item. /// </summary> - void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); + void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); /// <summary> @@ -520,12 +520,12 @@ namespace MediaBrowser.Controller.Library void UpdateMediaPath(string virtualFolderName, MediaPathInfo path); void RemoveMediaPath(string virtualFolderName, string path); - QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query); int GetCount(InternalItemsQuery query); diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 5156fce11..47e0f3453 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Persistence /// </summary> /// <param name="items">The items.</param> /// <param name="cancellationToken">The cancellation token.</param> - void SaveItems(List<BaseItem> items, CancellationToken cancellationToken); + void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken); void SaveImages(BaseItem item); @@ -141,12 +141,12 @@ namespace MediaBrowser.Controller.Persistence int GetCount(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); - QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query); + QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query); List<string> GetMusicGenreNames(); List<string> GetStudioNames(); diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs index ce4ef1cfe..6a1a0f090 100644 --- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs +++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs @@ -7,20 +7,6 @@ namespace MediaBrowser.Model.Configuration /// </summary> public class BaseApplicationConfiguration { - // TODO: @bond Remove? - /// <summary> - /// Gets or sets a value indicating whether [enable debug level logging]. - /// </summary> - /// <value><c>true</c> if [enable debug level logging]; otherwise, <c>false</c>.</value> - public bool EnableDebugLevelLogging { get; set; } - - /// <summary> - /// Enable automatically and silently updating of the application - /// </summary> - /// <value><c>true</c> if [enable auto update]; otherwise, <c>false</c>.</value> - public bool EnableAutoUpdate { get; set; } - - // TODO: @bond Remove? /// <summary> /// The number of days we should retain log files /// </summary> @@ -44,7 +30,6 @@ namespace MediaBrowser.Model.Configuration /// </summary> public BaseApplicationConfiguration() { - EnableAutoUpdate = true; LogFileRetentionDays = 3; } } diff --git a/MediaBrowser.Model/Serialization/IJsonSerializer.cs b/MediaBrowser.Model/Serialization/IJsonSerializer.cs index ae0cf6f36..18f51f652 100644 --- a/MediaBrowser.Model/Serialization/IJsonSerializer.cs +++ b/MediaBrowser.Model/Serialization/IJsonSerializer.cs @@ -15,6 +15,14 @@ namespace MediaBrowser.Model.Serialization void SerializeToStream(object obj, Stream stream); /// <summary> + /// Serializes to stream. + /// </summary> + /// <param name="obj">The obj.</param> + /// <param name="stream">The stream.</param> + /// <exception cref="ArgumentNullException">obj</exception> + void SerializeToStream<T>(T obj, Stream stream); + + /// <summary> /// Serializes to file. /// </summary> /// <param name="obj">The obj.</param> diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index d9ed68b27..581a1069c 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -60,12 +60,6 @@ namespace MediaBrowser.Model.System /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value> public bool CanSelfRestart { get; set; } - /// <summary> - /// Gets or sets a value indicating whether this instance can self update. - /// </summary> - /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value> - public bool CanSelfUpdate { get; set; } - public bool CanLaunchWebBrowser { get; set; } /// <summary> |
