diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1249de8f3..758202af6 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -20,6 +20,7 @@ - [RazeLighter777](https://github.com/RazeLighter777) - [WillWill56](https://github.com/WillWill56) - [Liggy](https://github.com/Liggy) + - [fruhnow](https://github.com/fruhnow) # Emby Contributors 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("" + trackString + ""); - 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("" + xml + "") + .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 GetNotificationTypes() { - var knownTypes = new List + 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 /// 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("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 _itemsAdded = new List(); - 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 @@ -72,12 +72,6 @@ namespace Emby.Server.Implementations.AppBase /// The plugin configurations path. public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations"); - /// - /// Gets the path to where temporary update files will be stored - /// - /// The plugin configurations path. - public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates"); - /// /// Gets the path to the log directory /// 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 /// true if this instance can self restart; otherwise, false. public abstract bool CanSelfRestart { get; } - /// - /// Gets or sets a value indicating whether this instance can self update. - /// - /// true if this instance can self update; otherwise, false. - 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, @@ -1755,21 +1748,6 @@ namespace Emby.Server.Implementations Plugins = list.ToArray(); } - /// - /// Updates the application. - /// - /// The package that contains the update - /// The cancellation token. - /// The progress. - public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress progress) - { - await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false); - - HasUpdateAvailable = false; - - OnApplicationUpdated(package); - } - /// /// This returns localhost in the case of no external dns, and the hostname if the /// dns is prefixed with a valid Uri prefix. 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 { item }, cancellationToken); + SaveItems(new [] { item }, cancellationToken); } public void SaveImages(BaseItem item) @@ -576,7 +576,7 @@ namespace Emby.Server.Implementations.Data /// or /// cancellationToken /// - public void SaveItems(List items, CancellationToken cancellationToken) + public void SaveItems(IEnumerable items, CancellationToken cancellationToken) { if (items == null) { @@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.Data CheckDisposed(); - var tuples = new List, BaseItem, string, List>>(); + var tuples = new List<(BaseItem, List, BaseItem, string, List)>(); 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, string, List>(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, BaseItem, string, List>> tuples) + private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List, BaseItem, string, List)> 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 /// 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(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 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 static readonly HashSet _programExcludeParentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Series", + "Season", + "MusicAlbum", + "MusicArtist", + "PhotoAlbum" + }; + + private static readonly HashSet _programTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Program", + "TvChannel", + "LiveTvProgram", + "LiveTvTvChannel" + }; + private bool HasProgramAttributes(InternalItemsQuery query) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; - - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + 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 static readonly HashSet _serviceTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "TvChannel", + "LiveTvTvChannel" + }; + private bool HasServiceName(InternalItemsQuery query) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; - - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + 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 static readonly HashSet _startDateTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Program", + "LiveTvProgram" + }; + private bool HasStartDate(InternalItemsQuery query) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; - - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + 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 static readonly HashSet _artistExcludeParentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Series", + "Season", + "PhotoAlbum" + }; + + private static readonly HashSet _artistsTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Audio", + "MusicAlbum", + "MusicVideo", + "AudioBook", + "AudioPodcast" + }; + private bool HasArtistFields(InternalItemsQuery query) { - var excludeParentTypes = new string[] - { - "Series", - "Season", - "PhotoAlbum" - }; - - if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + 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 _seriesTypes = new HashSet(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 GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable 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 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("SimilarityScore", SortOrder.Descending)); - orderBy.Add(new ValueTuple(ItemSortBy.Random, SortOrder.Ascending)); + int oldLen = query.OrderBy.Length; + + 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 + { + query.OrderBy = new [] + { + ("SearchScore", SortOrder.Descending), + (ItemSortBy.SortName, SortOrder.Ascending) + }; } - if (!string.IsNullOrEmpty(query.SearchTerm)) - { - orderBy = new List<(string, SortOrder)>(); - orderBy.Add(new ValueTuple("SearchScore", SortOrder.Descending)); - orderBy.Add(new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending)); - } + var orderBy = query.OrderBy; - query.OrderBy = orderBy.ToArray(); - - 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 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(); } 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> GetAllArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName); } - public QueryResult> GetArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName); } - public QueryResult> GetAlbumArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName); } - public QueryResult> GetStudios(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) { return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName); } - public QueryResult> GetGenres(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); } - public QueryResult> 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> 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() { @@ -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>(); - var result = new QueryResult>(); + 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(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 /// private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary 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(); } - 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(); } - 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(); } - 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(); } - 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(IRequest request, T dto, IDictionary 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(), contentType, contentLength, _logger); + var result = new StreamWriter(Array.Empty(), 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 /// public class StreamWriter : IAsyncStreamWriter, IHasHeaders { - private ILogger Logger { get; set; } - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// @@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.HttpServer /// The source. /// Type of the content. /// The logger. - 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 /// The source. /// Type of the content. /// The logger. - 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 /// id 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> 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> 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> 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> 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> 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> GetAlbumArtists(InternalItemsQuery query) + public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { if (query.User != null) { @@ -1808,18 +1806,16 @@ namespace Emby.Server.Implementations.Library /// Task. public void CreateItems(IEnumerable items, BaseItem parent, CancellationToken cancellationToken) { - var list = items.ToList(); + ItemRepository.SaveItems(items, cancellationToken); - ItemRepository.SaveItems(list, 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 /// /// Updates the item. /// - public void UpdateItems(List items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + public void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { foreach (var item in items) { @@ -1908,7 +1904,7 @@ namespace Emby.Server.Implementations.Library /// Task. public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - UpdateItems(new List { item }, parent, updateReason, cancellationToken); + UpdateItems(new [] { item }, parent, updateReason, cancellationToken); } /// @@ -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 /// 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 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 @@ -41,6 +41,27 @@ namespace Emby.Server.Implementations.Serialization ServiceStack.Text.JsonSerializer.SerializeToStream(obj, obj.GetType(), stream); } + /// + /// Serializes to stream. + /// + /// The obj. + /// The stream. + /// obj + public void SerializeToStream(T obj, Stream stream) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + ServiceStack.Text.JsonSerializer.SerializeToStream(obj, stream); + } + /// /// Serializes to file. /// 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; + + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + 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/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs index 6eee4cd12..9b0951857 100644 --- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs +++ b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs @@ -44,10 +44,11 @@ namespace Jellyfin.Server.SocketSharp socket.OnMessage += OnSocketMessage; socket.OnClose += OnSocketClose; socket.OnError += OnSocketError; - - WebSocket.ConnectAsServer(); } + public Task ConnectAsServerAsync() + => WebSocket.ConnectAsServer(); + public Task StartReceive() { return _taskCompletionSource.Task; @@ -133,7 +134,7 @@ namespace Jellyfin.Server.SocketSharp _cancellationTokenSource.Cancel(); - WebSocket.Close(); + WebSocket.CloseAsync().GetAwaiter().GetResult(); } _disposed = true; diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 58c4d38a2..736f9feef 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -69,7 +69,7 @@ namespace Jellyfin.Server.SocketSharp { if (_listener == null) { - _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment); + _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); } _listener.EnableDualMode = _enableDualMode; @@ -79,22 +79,14 @@ namespace Jellyfin.Server.SocketSharp _listener.LoadCert(_certificate); } - foreach (var prefix in urlPrefixes) - { - _logger.LogInformation("Adding HttpListener prefix " + prefix); - _listener.Prefixes.Add(prefix); - } + _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); + _listener.Prefixes.AddRange(urlPrefixes); - _listener.OnContext = ProcessContext; + _listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); _listener.Start(); } - private void ProcessContext(HttpListenerContext context) - { - _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken).ConfigureAwait(false)); - } - private static void LogRequest(ILogger logger, HttpListenerRequest request) { var url = request.Url.ToString(); @@ -151,10 +143,7 @@ namespace Jellyfin.Server.SocketSharp Endpoint = endpoint }; - if (WebSocketConnecting != null) - { - WebSocketConnecting(connectingArgs); - } + WebSocketConnecting?.Invoke(connectingArgs); if (connectingArgs.AllowConnection) { @@ -165,6 +154,7 @@ namespace Jellyfin.Server.SocketSharp if (WebSocketConnected != null) { var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger); + await socket.ConnectAsServerAsync().ConfigureAwait(false); WebSocketConnected(new WebSocketConnectEventArgs { @@ -174,7 +164,7 @@ namespace Jellyfin.Server.SocketSharp Endpoint = endpoint }); - await ReceiveWebSocket(ctx, socket).ConfigureAwait(false); + await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } } else @@ -192,7 +182,7 @@ namespace Jellyfin.Server.SocketSharp } } - private async Task ReceiveWebSocket(HttpListenerContext ctx, SharpWebSocket socket) + private async Task ReceiveWebSocketAsync(HttpListenerContext ctx, SharpWebSocket socket) { try { 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 format = httpReq.QueryString["format"]; if (format == null) { const int formatMaxLength = 4; - var pi = httpReq.PathInfo; + ReadOnlySpan 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 LeftPart(ReadOnlySpan 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(); + } + 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> 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> GetItems(GetItemsByName request, InternalItemsQuery query) + protected virtual QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) { - return new QueryResult>(); + 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(); } - 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(); } /// 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> 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..84475467f 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -12,6 +12,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { @@ -90,7 +91,7 @@ namespace MediaBrowser.Api.UserLibrary var options = GetDtoOptions(_authContext, request); - var ancestorIds = new List(); + var ancestorIds = Array.Empty(); var excludeFolderIds = user.Configuration.LatestItemsExcludes; if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) @@ -99,12 +100,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(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }, IsResumable = true, StartIndex = request.StartIndex, Limit = request.Limit, @@ -115,7 +116,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 +156,7 @@ namespace MediaBrowser.Api.UserLibrary /// The request. private QueryResult 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 +191,8 @@ namespace MediaBrowser.Api.UserLibrary /// private QueryResult 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; } @@ -227,6 +225,16 @@ namespace MediaBrowser.Api.UserLibrary request.IncludeItemTypes = "Playlist"; } + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)) + { + Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name); + return new QueryResult + { + Items = Array.Empty(), + TotalRecordCount = 0 + }; + } + if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null) { return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); 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> 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> 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> + return new QueryResult<(BaseItem, ItemCounts)> { TotalRecordCount = items.Count, - Items = items.Take(query.Limit ?? int.MaxValue).Select(i => new Tuple(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> 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 @@ -41,12 +41,6 @@ namespace MediaBrowser.Common.Configuration /// The plugin configurations path. string PluginConfigurationsPath { get; } - /// - /// Gets the path to where temporary update files will be stored - /// - /// The plugin configurations path. - string TempUpdatePath { get; } - /// /// Gets the path to the log directory /// 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 @@ -72,12 +72,6 @@ namespace MediaBrowser.Common /// The application user agent. string ApplicationUserAgent { get; } - /// - /// Gets or sets a value indicating whether this instance can self update. - /// - /// true if this instance can self update; otherwise, false. - bool CanSelfUpdate { get; } - /// /// Gets the exports. /// @@ -86,12 +80,6 @@ namespace MediaBrowser.Common /// IEnumerable{``0}. IEnumerable GetExports(bool manageLifetime = true); - /// - /// Updates the application. - /// - /// Task. - Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress progress); - /// /// Resolves this instance. /// 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 /// /// Updates the item. /// - void UpdateItems(List items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); + void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); /// @@ -520,12 +520,12 @@ namespace MediaBrowser.Controller.Library void UpdateMediaPath(string virtualFolderName, MediaPathInfo path); void RemoveMediaPath(string virtualFolderName, string path); - QueryResult> GetGenres(InternalItemsQuery query); - QueryResult> GetMusicGenres(InternalItemsQuery query); - QueryResult> GetStudios(InternalItemsQuery query); - QueryResult> GetArtists(InternalItemsQuery query); - QueryResult> GetAlbumArtists(InternalItemsQuery query); - QueryResult> 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 /// /// The items. /// The cancellation token. - void SaveItems(List items, CancellationToken cancellationToken); + void SaveItems(IEnumerable items, CancellationToken cancellationToken); void SaveImages(BaseItem item); @@ -141,12 +141,12 @@ namespace MediaBrowser.Controller.Persistence int GetCount(InternalItemsQuery query); - QueryResult> GetGenres(InternalItemsQuery query); - QueryResult> GetMusicGenres(InternalItemsQuery query); - QueryResult> GetStudios(InternalItemsQuery query); - QueryResult> GetArtists(InternalItemsQuery query); - QueryResult> GetAlbumArtists(InternalItemsQuery query); - QueryResult> 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 GetMusicGenreNames(); List 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 /// public class BaseApplicationConfiguration { - // TODO: @bond Remove? - /// - /// Gets or sets a value indicating whether [enable debug level logging]. - /// - /// true if [enable debug level logging]; otherwise, false. - public bool EnableDebugLevelLogging { get; set; } - - /// - /// Enable automatically and silently updating of the application - /// - /// true if [enable auto update]; otherwise, false. - public bool EnableAutoUpdate { get; set; } - - // TODO: @bond Remove? /// /// The number of days we should retain log files /// @@ -44,7 +30,6 @@ namespace MediaBrowser.Model.Configuration /// 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 @@ -14,6 +14,14 @@ namespace MediaBrowser.Model.Serialization /// obj void SerializeToStream(object obj, Stream stream); + /// + /// Serializes to stream. + /// + /// The obj. + /// The stream. + /// obj + void SerializeToStream(T obj, Stream stream); + /// /// Serializes to file. /// 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 /// true if this instance can self restart; otherwise, false. public bool CanSelfRestart { get; set; } - /// - /// Gets or sets a value indicating whether this instance can self update. - /// - /// true if this instance can self update; otherwise, false. - public bool CanSelfUpdate { get; set; } - public bool CanLaunchWebBrowser { get; set; } /// diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs deleted file mode 100644 index 10ff2515c..000000000 --- a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs +++ /dev/null @@ -1,402 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -//TODO Fix namespace or replace -namespace Priority_Queue -{ - /// - /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp - /// A copy of StablePriorityQueue which also has generic priority-type - /// - /// The values in the queue. Must extend the GenericPriorityQueue class - /// The priority-type. Must extend IComparable<TPriority> - public sealed class GenericPriorityQueue : IFixedSizePriorityQueue - where TItem : GenericPriorityQueueNode - where TPriority : IComparable - { - private int _numNodes; - private TItem[] _nodes; - private long _numNodesEverEnqueued; - - /// - /// Instantiate a new Priority Queue - /// - /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) - public GenericPriorityQueue(int maxNodes) - { -#if DEBUG - if (maxNodes <= 0) - { - throw new InvalidOperationException("New queue size cannot be smaller than 1"); - } -#endif - - _numNodes = 0; - _nodes = new TItem[maxNodes + 1]; - _numNodesEverEnqueued = 0; - } - - /// - /// Returns the number of nodes in the queue. - /// O(1) - /// - public int Count => _numNodes; - - /// - /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), - /// attempting to enqueue another item will cause undefined behavior. O(1) - /// - public int MaxSize => _nodes.Length - 1; - - /// - /// Removes every node from the queue. - /// O(n) (So, don't do this often!) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() - { - Array.Clear(_nodes, 1, _numNodes); - _numNodes = 0; - } - - /// - /// Returns (in O(1)!) whether the given node is in the queue. O(1) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(TItem node) - { -#if DEBUG - if (node == null) - { - throw new ArgumentNullException(nameof(node)); - } - if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length) - { - throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?"); - } -#endif - - return (_nodes[node.QueueIndex] == node); - } - - /// - /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. - /// If the queue is full, the result is undefined. - /// If the node is already enqueued, the result is undefined. - /// O(log n) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Enqueue(TItem node, TPriority priority) - { -#if DEBUG - if (node == null) - { - throw new ArgumentNullException(nameof(node)); - } - if (_numNodes >= _nodes.Length - 1) - { - throw new InvalidOperationException("Queue is full - node cannot be added: " + node); - } - if (Contains(node)) - { - throw new InvalidOperationException("Node is already enqueued: " + node); - } -#endif - - node.Priority = priority; - _numNodes++; - _nodes[_numNodes] = node; - node.QueueIndex = _numNodes; - node.InsertionIndex = _numNodesEverEnqueued++; - CascadeUp(_nodes[_numNodes]); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Swap(TItem node1, TItem node2) - { - //Swap the nodes - _nodes[node1.QueueIndex] = node2; - _nodes[node2.QueueIndex] = node1; - - //Swap their indicies - int temp = node1.QueueIndex; - node1.QueueIndex = node2.QueueIndex; - node2.QueueIndex = temp; - } - - //Performance appears to be slightly better when this is NOT inlined o_O - private void CascadeUp(TItem node) - { - //aka Heapify-up - int parent = node.QueueIndex / 2; - while (parent >= 1) - { - var parentNode = _nodes[parent]; - if (HasHigherPriority(parentNode, node)) - break; - - //Node has lower priority value, so move it up the heap - Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown() - - parent = node.QueueIndex / 2; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CascadeDown(TItem node) - { - //aka Heapify-down - TItem newParent; - int finalQueueIndex = node.QueueIndex; - while (true) - { - newParent = node; - int childLeftIndex = 2 * finalQueueIndex; - - //Check if the left-child is higher-priority than the current node - if (childLeftIndex > _numNodes) - { - //This could be placed outside the loop, but then we'd have to check newParent != node twice - node.QueueIndex = finalQueueIndex; - _nodes[finalQueueIndex] = node; - break; - } - - var childLeft = _nodes[childLeftIndex]; - if (HasHigherPriority(childLeft, newParent)) - { - newParent = childLeft; - } - - //Check if the right-child is higher-priority than either the current node or the left child - int childRightIndex = childLeftIndex + 1; - if (childRightIndex <= _numNodes) - { - var childRight = _nodes[childRightIndex]; - if (HasHigherPriority(childRight, newParent)) - { - newParent = childRight; - } - } - - //If either of the children has higher (smaller) priority, swap and continue cascading - if (newParent != node) - { - //Move new parent to its new index. node will be moved once, at the end - //Doing it this way is one less assignment operation than calling Swap() - _nodes[finalQueueIndex] = newParent; - - int temp = newParent.QueueIndex; - newParent.QueueIndex = finalQueueIndex; - finalQueueIndex = temp; - } - else - { - //See note above - node.QueueIndex = finalQueueIndex; - _nodes[finalQueueIndex] = node; - break; - } - } - } - - /// - /// Returns true if 'higher' has higher priority than 'lower', false otherwise. - /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool HasHigherPriority(TItem higher, TItem lower) - { - var cmp = higher.Priority.CompareTo(lower.Priority); - return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex)); - } - - /// - /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. - /// If queue is empty, result is undefined - /// O(log n) - /// - public bool TryDequeue(out TItem item) - { - if (_numNodes <= 0) - { - item = default(TItem); - return false; - } - -#if DEBUG - - if (!IsValidQueue()) - { - throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" + - "Or add the same node to two different queues?)"); - } -#endif - - var returnMe = _nodes[1]; - Remove(returnMe); - item = returnMe; - return true; - } - - /// - /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. - /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior - /// O(n) - /// - public void Resize(int maxNodes) - { -#if DEBUG - if (maxNodes <= 0) - { - throw new InvalidOperationException("Queue size cannot be smaller than 1"); - } - - if (maxNodes < _numNodes) - { - throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes"); - } -#endif - - TItem[] newArray = new TItem[maxNodes + 1]; - int highestIndexToCopy = Math.Min(maxNodes, _numNodes); - for (int i = 1; i <= highestIndexToCopy; i++) - { - newArray[i] = _nodes[i]; - } - _nodes = newArray; - } - - /// - /// Returns the head of the queue, without removing it (use Dequeue() for that). - /// If the queue is empty, behavior is undefined. - /// O(1) - /// - public TItem First - { - get - { -#if DEBUG - if (_numNodes <= 0) - { - throw new InvalidOperationException("Cannot call .First on an empty queue"); - } -#endif - - return _nodes[1]; - } - } - - /// - /// This method must be called on a node every time its priority changes while it is in the queue. - /// Forgetting to call this method will result in a corrupted queue! - /// Calling this method on a node not in the queue results in undefined behavior - /// O(log n) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdatePriority(TItem node, TPriority priority) - { -#if DEBUG - if (node == null) - { - throw new ArgumentNullException(nameof(node)); - } - if (!Contains(node)) - { - throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node); - } -#endif - - node.Priority = priority; - OnNodeUpdated(node); - } - - private void OnNodeUpdated(TItem node) - { - //Bubble the updated node up or down as appropriate - int parentIndex = node.QueueIndex / 2; - var parentNode = _nodes[parentIndex]; - - if (parentIndex > 0 && HasHigherPriority(node, parentNode)) - { - CascadeUp(node); - } - else - { - //Note that CascadeDown will be called if parentNode == node (that is, node is the root) - CascadeDown(node); - } - } - - /// - /// Removes a node from the queue. The node does not need to be the head of the queue. - /// If the node is not in the queue, the result is undefined. If unsure, check Contains() first - /// O(log n) - /// - public void Remove(TItem node) - { -#if DEBUG - if (node == null) - { - throw new ArgumentNullException(nameof(node)); - } - if (!Contains(node)) - { - throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node); - } -#endif - - //If the node is already the last node, we can remove it immediately - if (node.QueueIndex == _numNodes) - { - _nodes[_numNodes] = null; - _numNodes--; - return; - } - - //Swap the node with the last node - var formerLastNode = _nodes[_numNodes]; - Swap(node, formerLastNode); - _nodes[_numNodes] = null; - _numNodes--; - - //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate - OnNodeUpdated(formerLastNode); - } - - public IEnumerator GetEnumerator() - { - for (int i = 1; i <= _numNodes; i++) - yield return _nodes[i]; - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Should not be called in production code. - /// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue. - /// - public bool IsValidQueue() - { - for (int i = 1; i < _nodes.Length; i++) - { - if (_nodes[i] != null) - { - int childLeftIndex = 2 * i; - if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i])) - return false; - - int childRightIndex = childLeftIndex + 1; - if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i])) - return false; - } - } - return true; - } - } -} diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs deleted file mode 100644 index b45ae0fd8..000000000 --- a/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Priority_Queue -{ - /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp - public class GenericPriorityQueueNode - { - /// - /// The Priority to insert this node at. Must be set BEFORE adding a node to the queue (ideally just once, in the node's constructor). - /// Should not be manually edited once the node has been enqueued - use queue.UpdatePriority() instead - /// - public TPriority Priority { get; protected internal set; } - - /// - /// Represents the current position in the queue - /// - public int QueueIndex { get; internal set; } - - /// - /// Represents the order the node was inserted in - /// - public long InsertionIndex { get; internal set; } - } -} diff --git a/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs b/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs deleted file mode 100644 index 509d98e42..000000000 --- a/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Priority_Queue -{ - /// - /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp - /// A helper-interface only needed to make writing unit tests a bit easier (hence the 'internal' access modifier) - /// - internal interface IFixedSizePriorityQueue : IPriorityQueue - where TPriority : IComparable - { - /// - /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. - /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior - /// - void Resize(int maxNodes); - - /// - /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), - /// attempting to enqueue another item will cause undefined behavior. - /// - int MaxSize { get; } - } -} diff --git a/MediaBrowser.Providers/Manager/IPriorityQueue.cs b/MediaBrowser.Providers/Manager/IPriorityQueue.cs deleted file mode 100644 index dc319a7f8..000000000 --- a/MediaBrowser.Providers/Manager/IPriorityQueue.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Priority_Queue -{ - /// - /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp - /// The IPriorityQueue interface. This is mainly here for purists, and in case I decide to add more implementations later. - /// For speed purposes, it is actually recommended that you *don't* access the priority queue through this interface, since the JIT can - /// (theoretically?) optimize method calls from concrete-types slightly better. - /// - public interface IPriorityQueue : IEnumerable - where TPriority : IComparable - { - /// - /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. - /// See implementation for how duplicates are handled. - /// - void Enqueue(TItem node, TPriority priority); - - /// - /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. - /// - bool TryDequeue(out TItem item); - - /// - /// Removes every node from the queue. - /// - void Clear(); - - /// - /// Returns whether the given node is in the queue. - /// - bool Contains(TItem node); - - /// - /// Removes a node from the queue. The node does not need to be the head of the queue. - /// - void Remove(TItem node); - - /// - /// Call this method to change the priority of a node. - /// - void UpdatePriority(TItem node, TPriority priority); - - /// - /// Returns the head of the queue, without removing it (use Dequeue() for that). - /// - TItem First { get; } - - /// - /// Returns the number of nodes in the queue. - /// - int Count { get; } - } -} diff --git a/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs b/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs deleted file mode 100644 index d064312cf..000000000 --- a/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Priority_Queue -{ - /// - /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp - /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than - /// FastPriorityQueue - /// - /// The type to enqueue - /// The priority-type to use for nodes. Must extend IComparable<TPriority> - public class SimplePriorityQueue : IPriorityQueue - where TPriority : IComparable - { - private class SimpleNode : GenericPriorityQueueNode - { - public TItem Data { get; private set; } - - public SimpleNode(TItem data) - { - Data = data; - } - } - - private const int INITIAL_QUEUE_SIZE = 10; - private readonly GenericPriorityQueue _queue; - - public SimplePriorityQueue() - { - _queue = new GenericPriorityQueue(INITIAL_QUEUE_SIZE); - } - - /// - /// Given an item of type T, returns the exist SimpleNode in the queue - /// - private SimpleNode GetExistingNode(TItem item) - { - var comparer = EqualityComparer.Default; - foreach (var node in _queue) - { - if (comparer.Equals(node.Data, item)) - { - return node; - } - } - throw new InvalidOperationException("Item cannot be found in queue: " + item); - } - - /// - /// Returns the number of nodes in the queue. - /// O(1) - /// - public int Count - { - get - { - lock (_queue) - { - return _queue.Count; - } - } - } - - - /// - /// Returns the head of the queue, without removing it (use Dequeue() for that). - /// Throws an exception when the queue is empty. - /// O(1) - /// - public TItem First - { - get - { - lock (_queue) - { - if (_queue.Count <= 0) - { - throw new InvalidOperationException("Cannot call .First on an empty queue"); - } - - SimpleNode first = _queue.First; - return (first != null ? first.Data : default(TItem)); - } - } - } - - /// - /// Removes every node from the queue. - /// O(n) - /// - public void Clear() - { - lock (_queue) - { - _queue.Clear(); - } - } - - /// - /// Returns whether the given item is in the queue. - /// O(n) - /// - public bool Contains(TItem item) - { - lock (_queue) - { - var comparer = EqualityComparer.Default; - foreach (var node in _queue) - { - if (comparer.Equals(node.Data, item)) - { - return true; - } - } - return false; - } - } - - /// - /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. - /// If queue is empty, throws an exception - /// O(log n) - /// - public bool TryDequeue(out TItem item) - { - lock (_queue) - { - if (_queue.Count <= 0) - { - item = default(TItem); - return false; - } - - if (_queue.TryDequeue(out SimpleNode node)) - { - item = node.Data; - return true; - } - - item = default(TItem); - return false; - } - } - - /// - /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. - /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'. - /// Duplicates are allowed. - /// O(log n) - /// - public void Enqueue(TItem item, TPriority priority) - { - lock (_queue) - { - var node = new SimpleNode(item); - if (_queue.Count == _queue.MaxSize) - { - _queue.Resize(_queue.MaxSize * 2 + 1); - } - _queue.Enqueue(node, priority); - } - } - - /// - /// Removes an item from the queue. The item does not need to be the head of the queue. - /// If the item is not in the queue, an exception is thrown. If unsure, check Contains() first. - /// If multiple copies of the item are enqueued, only the first one is removed. - /// O(n) - /// - public void Remove(TItem item) - { - lock (_queue) - { - try - { - _queue.Remove(GetExistingNode(item)); - } - catch (InvalidOperationException ex) - { - throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item, ex); - } - } - } - - /// - /// Call this method to change the priority of an item. - /// Calling this method on a item not in the queue will throw an exception. - /// If the item is enqueued multiple times, only the first one will be updated. - /// (If your requirements are complex enough that you need to enqueue the same item multiple times and be able - /// to update all of them, please wrap your items in a wrapper class so they can be distinguished). - /// O(n) - /// - public void UpdatePriority(TItem item, TPriority priority) - { - lock (_queue) - { - try - { - SimpleNode updateMe = GetExistingNode(item); - _queue.UpdatePriority(updateMe, priority); - } - catch (InvalidOperationException ex) - { - throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item, ex); - } - } - } - - public IEnumerator GetEnumerator() - { - var queueData = new List(); - lock (_queue) - { - //Copy to a separate list because we don't want to 'yield return' inside a lock - foreach (var node in _queue) - { - queueData.Add(node.Data); - } - } - - return queueData.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public bool IsValidQueue() - { - lock (_queue) - { - return _queue.IsValidQueue(); - } - } - } - - /// - /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than - /// FastPriorityQueue - /// This class is kept here for backwards compatibility. It's recommended you use Simple - /// - /// The type to enqueue - public class SimplePriorityQueue : SimplePriorityQueue { } -} diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index e6ef889c3..7b8d629ee 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -1,4 +1,4 @@ - + @@ -11,6 +11,7 @@ + diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs index a02b48061..2b3c67071 100644 --- a/SocketHttpListener/Ext.cs +++ b/SocketHttpListener/Ext.cs @@ -74,18 +74,20 @@ namespace SocketHttpListener } } - private static byte[] readBytes(this Stream stream, byte[] buffer, int offset, int length) + private static async Task ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length) { - var len = stream.Read(buffer, offset, length); + var len = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false); if (len < 1) return buffer.SubArray(0, offset); var tmp = 0; while (len < length) { - tmp = stream.Read(buffer, offset + len, length - len); + tmp = await stream.ReadAsync(buffer, offset + len, length - len).ConfigureAwait(false); if (tmp < 1) + { break; + } len += tmp; } @@ -95,10 +97,9 @@ namespace SocketHttpListener : buffer; } - private static bool readBytes( - this Stream stream, byte[] buffer, int offset, int length, Stream dest) + private static async Task ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest) { - var bytes = stream.readBytes(buffer, offset, length); + var bytes = await stream.ReadBytesAsync(buffer, offset, length).ConfigureAwait(false); var len = bytes.Length; dest.Write(bytes, 0, len); @@ -109,16 +110,16 @@ namespace SocketHttpListener #region Internal Methods - internal static byte[] Append(this ushort code, string reason) + internal static async Task AppendAsync(this ushort code, string reason) { using (var buffer = new MemoryStream()) { var tmp = code.ToByteArrayInternally(ByteOrder.Big); - buffer.Write(tmp, 0, 2); + await buffer.WriteAsync(tmp, 0, 2).ConfigureAwait(false); if (reason != null && reason.Length > 0) { tmp = Encoding.UTF8.GetBytes(reason); - buffer.Write(tmp, 0, tmp.Length); + await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false); } return buffer.ToArray(); @@ -331,12 +332,10 @@ namespace SocketHttpListener : string.Format("\"{0}\"", value.Replace("\"", "\\\"")); } - internal static byte[] ReadBytes(this Stream stream, int length) - { - return stream.readBytes(new byte[length], 0, length); - } + internal static Task ReadBytesAsync(this Stream stream, int length) + => stream.ReadBytesAsync(new byte[length], 0, length); - internal static byte[] ReadBytes(this Stream stream, long length, int bufferLength) + internal static async Task ReadBytesAsync(this Stream stream, long length, int bufferLength) { using (var result = new MemoryStream()) { @@ -347,7 +346,7 @@ namespace SocketHttpListener var end = false; for (long i = 0; i < count; i++) { - if (!stream.readBytes(buffer, 0, bufferLength, result)) + if (!await stream.ReadBytesAsync(buffer, 0, bufferLength, result).ConfigureAwait(false)) { end = true; break; @@ -355,26 +354,14 @@ namespace SocketHttpListener } if (!end && rem > 0) - stream.readBytes(new byte[rem], 0, rem, result); + { + await stream.ReadBytesAsync(new byte[rem], 0, rem, result).ConfigureAwait(false); + } return result.ToArray(); } } - internal static async Task ReadBytesAsync(this Stream stream, int length) - { - var buffer = new byte[length]; - - var len = await stream.ReadAsync(buffer, 0, length).ConfigureAwait(false); - var bytes = len < 1 - ? new byte[0] - : len < length - ? stream.readBytes(buffer, len, length - len) - : buffer; - - return bytes; - } - internal static string RemovePrefix(this string value, params string[] prefixes) { var i = 0; @@ -493,19 +480,16 @@ namespace SocketHttpListener return string.Format("{0}; {1}", m, parameters.ToString("; ")); } - internal static List ToList(this IEnumerable source) - { - return new List(source); - } - internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder) { - return BitConverter.ToUInt16(src.ToHostOrder(srcOrder), 0); + src.ToHostOrder(srcOrder); + return BitConverter.ToUInt16(src, 0); } internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder) { - return BitConverter.ToUInt64(src.ToHostOrder(srcOrder), 0); + src.ToHostOrder(srcOrder); + return BitConverter.ToUInt64(src, 0); } internal static string TrimEndSlash(this string value) @@ -852,14 +836,17 @@ namespace SocketHttpListener /// /// is . /// - public static byte[] ToHostOrder(this byte[] src, ByteOrder srcOrder) + public static void ToHostOrder(this byte[] src, ByteOrder srcOrder) { if (src == null) + { throw new ArgumentNullException(nameof(src)); + } - return src.Length > 1 && !srcOrder.IsHostOrder() - ? src.Reverse() - : src; + if (src.Length > 1 && !srcOrder.IsHostOrder()) + { + Array.Reverse(src); + } } /// diff --git a/SocketHttpListener/Net/HttpListener.cs b/SocketHttpListener/Net/HttpListener.cs index b80180679..f17036a21 100644 --- a/SocketHttpListener/Net/HttpListener.cs +++ b/SocketHttpListener/Net/HttpListener.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; using System.Net; using System.Security.Cryptography.X509Certificates; -using MediaBrowser.Common.Net; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; @@ -18,47 +17,55 @@ namespace SocketHttpListener.Net internal ISocketFactory SocketFactory { get; private set; } internal IFileSystem FileSystem { get; private set; } internal IStreamHelper StreamHelper { get; private set; } - internal INetworkManager NetworkManager { get; private set; } internal IEnvironmentInfo EnvironmentInfo { get; private set; } public bool EnableDualMode { get; set; } - AuthenticationSchemes auth_schemes; - HttpListenerPrefixCollection prefixes; - AuthenticationSchemeSelector auth_selector; - string realm; - bool unsafe_ntlm_auth; - bool listening; - bool disposed; + private AuthenticationSchemes auth_schemes; + private HttpListenerPrefixCollection prefixes; + private AuthenticationSchemeSelector auth_selector; + private string realm; + private bool unsafe_ntlm_auth; + private bool listening; + private bool disposed; - Dictionary registry; // Dictionary - Dictionary connections; + private Dictionary registry; + private Dictionary connections; private ILogger _logger; private X509Certificate _certificate; public Action OnContext { get; set; } - public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, - INetworkManager networkManager, IStreamHelper streamHelper, IFileSystem fileSystem, + public HttpListener( + ILogger logger, + ICryptoProvider cryptoProvider, + ISocketFactory socketFactory, + IStreamHelper streamHelper, + IFileSystem fileSystem, IEnvironmentInfo environmentInfo) { _logger = logger; CryptoProvider = cryptoProvider; SocketFactory = socketFactory; - NetworkManager = networkManager; StreamHelper = streamHelper; FileSystem = fileSystem; EnvironmentInfo = environmentInfo; + prefixes = new HttpListenerPrefixCollection(logger, this); registry = new Dictionary(); connections = new Dictionary(); auth_schemes = AuthenticationSchemes.Anonymous; } - public HttpListener(ILogger logger, X509Certificate certificate, ICryptoProvider cryptoProvider, - ISocketFactory socketFactory, INetworkManager networkManager, IStreamHelper streamHelper, - IFileSystem fileSystem, IEnvironmentInfo environmentInfo) - : this(logger, cryptoProvider, socketFactory, networkManager, streamHelper, fileSystem, environmentInfo) + public HttpListener( + ILogger logger, + X509Certificate certificate, + ICryptoProvider cryptoProvider, + ISocketFactory socketFactory, + IStreamHelper streamHelper, + IFileSystem fileSystem, + IEnvironmentInfo environmentInfo) + : this(logger, cryptoProvider, socketFactory, streamHelper, fileSystem, environmentInfo) { _certificate = certificate; } diff --git a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs index 97dc6797c..400a1adb6 100644 --- a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs +++ b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs @@ -7,18 +7,18 @@ namespace SocketHttpListener.Net { public class HttpListenerPrefixCollection : ICollection, IEnumerable, IEnumerable { - List prefixes = new List(); - HttpListener listener; + private List _prefixes = new List(); + private HttpListener _listener; private ILogger _logger; internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener) { _logger = logger; - this.listener = listener; + _listener = listener; } - public int Count => prefixes.Count; + public int Count => _prefixes.Count; public bool IsReadOnly => false; @@ -26,61 +26,90 @@ namespace SocketHttpListener.Net public void Add(string uriPrefix) { - listener.CheckDisposed(); + _listener.CheckDisposed(); //ListenerPrefix.CheckUri(uriPrefix); - if (prefixes.Contains(uriPrefix)) + if (_prefixes.Contains(uriPrefix)) + { return; + } - prefixes.Add(uriPrefix); - if (listener.IsListening) - HttpEndPointManager.AddPrefix(_logger, uriPrefix, listener); + _prefixes.Add(uriPrefix); + if (_listener.IsListening) + { + HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener); + } + } + + public void AddRange(IEnumerable uriPrefixes) + { + _listener.CheckDisposed(); + + foreach (var uriPrefix in uriPrefixes) + { + if (_prefixes.Contains(uriPrefix)) + { + continue; + } + + _prefixes.Add(uriPrefix); + if (_listener.IsListening) + { + HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener); + } + } } public void Clear() { - listener.CheckDisposed(); - prefixes.Clear(); - if (listener.IsListening) - HttpEndPointManager.RemoveListener(_logger, listener); + _listener.CheckDisposed(); + _prefixes.Clear(); + if (_listener.IsListening) + { + HttpEndPointManager.RemoveListener(_logger, _listener); + } } public bool Contains(string uriPrefix) { - listener.CheckDisposed(); - return prefixes.Contains(uriPrefix); + _listener.CheckDisposed(); + return _prefixes.Contains(uriPrefix); } public void CopyTo(string[] array, int offset) { - listener.CheckDisposed(); - prefixes.CopyTo(array, offset); + _listener.CheckDisposed(); + _prefixes.CopyTo(array, offset); } public void CopyTo(Array array, int offset) { - listener.CheckDisposed(); - ((ICollection)prefixes).CopyTo(array, offset); + _listener.CheckDisposed(); + ((ICollection)_prefixes).CopyTo(array, offset); } public IEnumerator GetEnumerator() { - return prefixes.GetEnumerator(); + return _prefixes.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { - return prefixes.GetEnumerator(); + return _prefixes.GetEnumerator(); } public bool Remove(string uriPrefix) { - listener.CheckDisposed(); + _listener.CheckDisposed(); if (uriPrefix == null) + { throw new ArgumentNullException(nameof(uriPrefix)); + } - bool result = prefixes.Remove(uriPrefix); - if (result && listener.IsListening) - HttpEndPointManager.RemovePrefix(_logger, uriPrefix, listener); + bool result = _prefixes.Remove(uriPrefix); + if (result && _listener.IsListening) + { + HttpEndPointManager.RemovePrefix(_logger, uriPrefix, _listener); + } return result; } diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs index 128bc8b97..0dcb6a64b 100644 --- a/SocketHttpListener/WebSocket.cs +++ b/SocketHttpListener/WebSocket.cs @@ -30,9 +30,9 @@ namespace SocketHttpListener private CookieCollection _cookies; private AutoResetEvent _exitReceiving; private object _forConn; - private object _forEvent; + private readonly SemaphoreSlim _forEvent = new SemaphoreSlim(1, 1); private object _forMessageEventQueue; - private object _forSend; + private readonly SemaphoreSlim _forSend = new SemaphoreSlim(1, 1); private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private Queue _messageEventQueue; private string _protocol; @@ -109,12 +109,15 @@ namespace SocketHttpListener #region Private Methods - private void close(CloseStatusCode code, string reason, bool wait) + private async Task CloseAsync(CloseStatusCode code, string reason, bool wait) { - close(new PayloadData(((ushort)code).Append(reason)), !code.IsReserved(), wait); + await CloseAsync(new PayloadData( + await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)), + !code.IsReserved(), + wait).ConfigureAwait(false); } - private void close(PayloadData payload, bool send, bool wait) + private async Task CloseAsync(PayloadData payload, bool send, bool wait) { lock (_forConn) { @@ -126,11 +129,12 @@ namespace SocketHttpListener _readyState = WebSocketState.CloseSent; } - var e = new CloseEventArgs(payload); - e.WasClean = - closeHandshake( + var e = new CloseEventArgs(payload) + { + WasClean = await CloseHandshakeAsync( send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null, - wait ? 1000 : 0); + wait ? 1000 : 0).ConfigureAwait(false) + }; _readyState = WebSocketState.Closed; try @@ -143,9 +147,9 @@ namespace SocketHttpListener } } - private bool closeHandshake(byte[] frameAsBytes, int millisecondsTimeout) + private async Task CloseHandshakeAsync(byte[] frameAsBytes, int millisecondsTimeout) { - var sent = frameAsBytes != null && writeBytes(frameAsBytes); + var sent = frameAsBytes != null && await WriteBytesAsync(frameAsBytes).ConfigureAwait(false); var received = millisecondsTimeout == 0 || (sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout)); @@ -189,11 +193,11 @@ namespace SocketHttpListener _context = null; } - private bool concatenateFragmentsInto(Stream dest) + private async Task ConcatenateFragmentsIntoAsync(Stream dest) { while (true) { - var frame = WebSocketFrame.Read(_stream, true); + var frame = await WebSocketFrame.ReadAsync(_stream, true).ConfigureAwait(false); if (frame.IsFinal) { /* FINAL */ @@ -221,7 +225,7 @@ namespace SocketHttpListener // CLOSE if (frame.IsClose) - return processCloseFrame(frame); + return await ProcessCloseFrameAsync(frame).ConfigureAwait(false); } else { @@ -236,10 +240,10 @@ namespace SocketHttpListener } // ? - return processUnsupportedFrame( + return await ProcessUnsupportedFrameAsync( frame, CloseStatusCode.IncorrectData, - "An incorrect data has been received while receiving fragmented data."); + "An incorrect data has been received while receiving fragmented data.").ConfigureAwait(false); } return true; @@ -299,44 +303,42 @@ namespace SocketHttpListener _compression = CompressionMethod.None; _cookies = new CookieCollection(); _forConn = new object(); - _forEvent = new object(); - _forSend = new object(); _messageEventQueue = new Queue(); _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot; _readyState = WebSocketState.Connecting; } - private void open() + private async Task OpenAsync() { try { startReceiving(); - lock (_forEvent) - { - try - { - if (OnOpen != null) - { - OnOpen(this, EventArgs.Empty); - } - } - catch (Exception ex) - { - processException(ex, "An exception has occurred while OnOpen."); - } - } } catch (Exception ex) { - processException(ex, "An exception has occurred while opening."); + await ProcessExceptionAsync(ex, "An exception has occurred while opening.").ConfigureAwait(false); + } + + await _forEvent.WaitAsync().ConfigureAwait(false); + try + { + OnOpen?.Invoke(this, EventArgs.Empty); + } + catch (Exception ex) + { + await ProcessExceptionAsync(ex, "An exception has occurred while OnOpen.").ConfigureAwait(false); + } + finally + { + _forEvent.Release(); } } - private bool processCloseFrame(WebSocketFrame frame) + private async Task ProcessCloseFrameAsync(WebSocketFrame frame) { var payload = frame.PayloadData; - close(payload, !payload.ContainsReservedCloseStatusCode, false); + await CloseAsync(payload, !payload.ContainsReservedCloseStatusCode, false).ConfigureAwait(false); return false; } @@ -352,7 +354,7 @@ namespace SocketHttpListener return true; } - private void processException(Exception exception, string message) + private async Task ProcessExceptionAsync(Exception exception, string message) { var code = CloseStatusCode.Abnormal; var reason = message; @@ -365,25 +367,31 @@ namespace SocketHttpListener error(message ?? code.GetMessage(), exception); if (_readyState == WebSocketState.Connecting) - Close(HttpStatusCode.BadRequest); + { + await CloseAsync(HttpStatusCode.BadRequest).ConfigureAwait(false); + } else - close(code, reason ?? code.GetMessage(), false); + { + await CloseAsync(code, reason ?? code.GetMessage(), false).ConfigureAwait(false); + } } - private bool processFragmentedFrame(WebSocketFrame frame) + private Task ProcessFragmentedFrameAsync(WebSocketFrame frame) { return frame.IsContinuation // Not first fragment - ? true - : processFragments(frame); + ? Task.FromResult(true) + : ProcessFragmentsAsync(frame); } - private bool processFragments(WebSocketFrame first) + private async Task ProcessFragmentsAsync(WebSocketFrame first) { using (var buff = new MemoryStream()) { buff.WriteBytes(first.PayloadData.ApplicationData); - if (!concatenateFragmentsInto(buff)) + if (!await ConcatenateFragmentsIntoAsync(buff).ConfigureAwait(false)) + { return false; + } byte[] data; if (_compression != CompressionMethod.None) @@ -412,36 +420,38 @@ namespace SocketHttpListener return true; } - private bool processUnsupportedFrame(WebSocketFrame frame, CloseStatusCode code, string reason) + private async Task ProcessUnsupportedFrameAsync(WebSocketFrame frame, CloseStatusCode code, string reason) { - processException(new WebSocketException(code, reason), null); + await ProcessExceptionAsync(new WebSocketException(code, reason), null).ConfigureAwait(false); return false; } - private bool processWebSocketFrame(WebSocketFrame frame) + private Task ProcessWebSocketFrameAsync(WebSocketFrame frame) { + // TODO: @bond change to if/else chain return frame.IsCompressed && _compression == CompressionMethod.None - ? processUnsupportedFrame( + ? ProcessUnsupportedFrameAsync( frame, CloseStatusCode.IncorrectData, "A compressed data has been received without available decompression method.") : frame.IsFragmented - ? processFragmentedFrame(frame) + ? ProcessFragmentedFrameAsync(frame) : frame.IsData - ? processDataFrame(frame) + ? Task.FromResult(processDataFrame(frame)) : frame.IsPing - ? processPingFrame(frame) + ? Task.FromResult(processPingFrame(frame)) : frame.IsPong - ? processPongFrame(frame) + ? Task.FromResult(processPongFrame(frame)) : frame.IsClose - ? processCloseFrame(frame) - : processUnsupportedFrame(frame, CloseStatusCode.PolicyViolation, null); + ? ProcessCloseFrameAsync(frame) + : ProcessUnsupportedFrameAsync(frame, CloseStatusCode.PolicyViolation, null); } - private bool send(Opcode opcode, Stream stream) + private async Task SendAsync(Opcode opcode, Stream stream) { - lock (_forSend) + await _forSend.WaitAsync().ConfigureAwait(false); + try { var src = stream; var compressed = false; @@ -454,7 +464,7 @@ namespace SocketHttpListener compressed = true; } - sent = send(opcode, Mask.Unmask, stream, compressed); + sent = await SendAsync(opcode, Mask.Unmask, stream, compressed).ConfigureAwait(false); if (!sent) error("Sending a data has been interrupted."); } @@ -472,16 +482,20 @@ namespace SocketHttpListener return sent; } + finally + { + _forSend.Release(); + } } - private bool send(Opcode opcode, Mask mask, Stream stream, bool compressed) + private async Task SendAsync(Opcode opcode, Mask mask, Stream stream, bool compressed) { var len = stream.Length; /* Not fragmented */ if (len == 0) - return send(Fin.Final, opcode, mask, new byte[0], compressed); + return await SendAsync(Fin.Final, opcode, mask, new byte[0], compressed).ConfigureAwait(false); var quo = len / FragmentLength; var rem = (int)(len % FragmentLength); @@ -490,26 +504,26 @@ namespace SocketHttpListener if (quo == 0) { buff = new byte[rem]; - return stream.Read(buff, 0, rem) == rem && - send(Fin.Final, opcode, mask, buff, compressed); + return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem && + await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false); } buff = new byte[FragmentLength]; if (quo == 1 && rem == 0) - return stream.Read(buff, 0, FragmentLength) == FragmentLength && - send(Fin.Final, opcode, mask, buff, compressed); + return await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) == FragmentLength && + await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false); /* Send fragmented */ // Begin - if (stream.Read(buff, 0, FragmentLength) != FragmentLength || - !send(Fin.More, opcode, mask, buff, compressed)) + if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength || + !await SendAsync(Fin.More, opcode, mask, buff, compressed).ConfigureAwait(false)) return false; var n = rem == 0 ? quo - 2 : quo - 1; for (long i = 0; i < n; i++) - if (stream.Read(buff, 0, FragmentLength) != FragmentLength || - !send(Fin.More, Opcode.Cont, mask, buff, compressed)) + if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength || + !await SendAsync(Fin.More, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false)) return false; // End @@ -518,98 +532,88 @@ namespace SocketHttpListener else buff = new byte[rem]; - return stream.Read(buff, 0, rem) == rem && - send(Fin.Final, Opcode.Cont, mask, buff, compressed); + return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem && + await SendAsync(Fin.Final, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false); } - private bool send(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed) + private Task SendAsync(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed) { lock (_forConn) { if (_readyState != WebSocketState.Open) { - return false; + return Task.FromResult(false); } - return writeBytes( + return WriteBytesAsync( WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray()); } } - private Task sendAsync(Opcode opcode, Stream stream) - { - var completionSource = new TaskCompletionSource(); - Task.Run(() => - { - try - { - send(opcode, stream); - completionSource.TrySetResult(true); - } - catch (Exception ex) - { - completionSource.TrySetException(ex); - } - }); - return completionSource.Task; - } - // As server - private bool sendHttpResponse(HttpResponse response) - { - return writeBytes(response.ToByteArray()); - } + private Task SendHttpResponseAsync(HttpResponse response) + => WriteBytesAsync(response.ToByteArray()); private void startReceiving() { if (_messageEventQueue.Count > 0) + { _messageEventQueue.Clear(); + } _exitReceiving = new AutoResetEvent(false); _receivePong = new AutoResetEvent(false); Action receive = null; - receive = () => WebSocketFrame.ReadAsync( - _stream, - true, - frame => - { - if (processWebSocketFrame(frame) && _readyState != WebSocketState.Closed) - { - receive(); + receive = async () => await WebSocketFrame.ReadAsync( + _stream, + true, + async frame => + { + if (await ProcessWebSocketFrameAsync(frame).ConfigureAwait(false) && _readyState != WebSocketState.Closed) + { + receive(); - if (!frame.IsData) - return; + if (!frame.IsData) + { + return; + } - lock (_forEvent) - { - try - { - var e = dequeueFromMessageEventQueue(); - if (e != null && _readyState == WebSocketState.Open) - OnMessage.Emit(this, e); - } - catch (Exception ex) - { - processException(ex, "An exception has occurred while OnMessage."); - } - } - } - else if (_exitReceiving != null) - { - _exitReceiving.Set(); - } - }, - ex => processException(ex, "An exception has occurred while receiving a message.")); + await _forEvent.WaitAsync().ConfigureAwait(false); + + try + { + var e = dequeueFromMessageEventQueue(); + if (e != null && _readyState == WebSocketState.Open) + { + OnMessage.Emit(this, e); + } + } + catch (Exception ex) + { + await ProcessExceptionAsync(ex, "An exception has occurred while OnMessage.").ConfigureAwait(false); + } + finally + { + _forEvent.Release(); + } + + } + else if (_exitReceiving != null) + { + _exitReceiving.Set(); + } + }, + async ex => await ProcessExceptionAsync(ex, "An exception has occurred while receiving a message.")).ConfigureAwait(false); receive(); } - private bool writeBytes(byte[] data) + private async Task WriteBytesAsync(byte[] data) { try { - _stream.Write(data, 0, data.Length); + await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); return true; } catch (Exception) @@ -623,10 +627,10 @@ namespace SocketHttpListener #region Internal Methods // As server - internal void Close(HttpResponse response) + internal async Task CloseAsync(HttpResponse response) { _readyState = WebSocketState.CloseSent; - sendHttpResponse(response); + await SendHttpResponseAsync(response).ConfigureAwait(false); closeServerResources(); @@ -634,22 +638,20 @@ namespace SocketHttpListener } // As server - internal void Close(HttpStatusCode code) - { - Close(createHandshakeCloseResponse(code)); - } + internal Task CloseAsync(HttpStatusCode code) + => CloseAsync(createHandshakeCloseResponse(code)); // As server - public void ConnectAsServer() + public async Task ConnectAsServer() { try { _readyState = WebSocketState.Open; - open(); + await OpenAsync().ConfigureAwait(false); } catch (Exception ex) { - processException(ex, "An exception has occurred while connecting."); + await ProcessExceptionAsync(ex, "An exception has occurred while connecting.").ConfigureAwait(false); } } @@ -660,18 +662,18 @@ namespace SocketHttpListener /// /// Closes the WebSocket connection, and releases all associated resources. /// - public void Close() + public Task CloseAsync() { var msg = _readyState.CheckIfClosable(); if (msg != null) { error(msg); - return; + return Task.CompletedTask; } var send = _readyState == WebSocketState.Open; - close(new PayloadData(), send, send); + return CloseAsync(new PayloadData(), send, send); } /// @@ -689,11 +691,11 @@ namespace SocketHttpListener /// /// A that represents the reason for the close. /// - public void Close(CloseStatusCode code, string reason) + public async Task CloseAsync(CloseStatusCode code, string reason) { byte[] data = null; var msg = _readyState.CheckIfClosable() ?? - (data = ((ushort)code).Append(reason)).CheckIfValidControlData("reason"); + (data = await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)).CheckIfValidControlData("reason"); if (msg != null) { @@ -703,7 +705,7 @@ namespace SocketHttpListener } var send = _readyState == WebSocketState.Open && !code.IsReserved(); - close(new PayloadData(data), send, send); + await CloseAsync(new PayloadData(data), send, send).ConfigureAwait(false); } /// @@ -728,7 +730,7 @@ namespace SocketHttpListener throw new Exception(msg); } - return sendAsync(Opcode.Binary, new MemoryStream(data)); + return SendAsync(Opcode.Binary, new MemoryStream(data)); } /// @@ -753,7 +755,7 @@ namespace SocketHttpListener throw new Exception(msg); } - return sendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data))); + return SendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data))); } #endregion @@ -768,7 +770,7 @@ namespace SocketHttpListener /// void IDisposable.Dispose() { - Close(CloseStatusCode.Away, null); + CloseAsync(CloseStatusCode.Away, null).GetAwaiter().GetResult(); } #endregion diff --git a/SocketHttpListener/WebSocketFrame.cs b/SocketHttpListener/WebSocketFrame.cs index 74ed23c45..8ec64026b 100644 --- a/SocketHttpListener/WebSocketFrame.cs +++ b/SocketHttpListener/WebSocketFrame.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; namespace SocketHttpListener { @@ -177,7 +178,7 @@ namespace SocketHttpListener return opcode == Opcode.Text || opcode == Opcode.Binary; } - private static WebSocketFrame read(byte[] header, Stream stream, bool unmask) + private static async Task ReadAsync(byte[] header, Stream stream, bool unmask) { /* Header */ @@ -229,7 +230,7 @@ namespace SocketHttpListener ? 2 : 8; - var extPayloadLen = size > 0 ? stream.ReadBytes(size) : new byte[0]; + var extPayloadLen = size > 0 ? await stream.ReadBytesAsync(size).ConfigureAwait(false) : Array.Empty(); if (size > 0 && extPayloadLen.Length != size) throw new WebSocketException( "The 'Extended Payload Length' of a frame cannot be read from the data source."); @@ -239,7 +240,7 @@ namespace SocketHttpListener /* Masking Key */ var masked = mask == Mask.Mask; - var maskingKey = masked ? stream.ReadBytes(4) : new byte[0]; + var maskingKey = masked ? await stream.ReadBytesAsync(4).ConfigureAwait(false) : Array.Empty(); if (masked && maskingKey.Length != 4) throw new WebSocketException( "The 'Masking Key' of a frame cannot be read from the data source."); @@ -264,8 +265,8 @@ namespace SocketHttpListener "The length of 'Payload Data' of a frame is greater than the allowable length."); data = payloadLen > 126 - ? stream.ReadBytes((long)len, 1024) - : stream.ReadBytes((int)len); + ? await stream.ReadBytesAsync((long)len, 1024).ConfigureAwait(false) + : await stream.ReadBytesAsync((int)len).ConfigureAwait(false); //if (data.LongLength != (long)len) // throw new WebSocketException( @@ -273,7 +274,7 @@ namespace SocketHttpListener } else { - data = new byte[0]; + data = Array.Empty(); } var payload = new PayloadData(data, masked); @@ -281,7 +282,7 @@ namespace SocketHttpListener { payload.Mask(maskingKey); frame._mask = Mask.Unmask; - frame._maskingKey = new byte[0]; + frame._maskingKey = Array.Empty(); } frame._payloadData = payload; @@ -302,10 +303,10 @@ namespace SocketHttpListener return new WebSocketFrame(Opcode.Close, mask, payload); } - internal static WebSocketFrame CreateCloseFrame(Mask mask, CloseStatusCode code, string reason) + internal static async Task CreateCloseFrameAsync(Mask mask, CloseStatusCode code, string reason) { return new WebSocketFrame( - Opcode.Close, mask, new PayloadData(((ushort)code).Append(reason))); + Opcode.Close, mask, new PayloadData(await ((ushort)code).AppendAsync(reason).ConfigureAwait(false))); } internal static WebSocketFrame CreatePingFrame(Mask mask) @@ -329,41 +330,39 @@ namespace SocketHttpListener return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed); } - internal static WebSocketFrame Read(Stream stream) - { - return Read(stream, true); - } + internal static Task ReadAsync(Stream stream) + => ReadAsync(stream, true); - internal static WebSocketFrame Read(Stream stream, bool unmask) + internal static async Task ReadAsync(Stream stream, bool unmask) { - var header = stream.ReadBytes(2); + var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); if (header.Length != 2) + { throw new WebSocketException( "The header part of a frame cannot be read from the data source."); + } - return read(header, stream, unmask); + return await ReadAsync(header, stream, unmask).ConfigureAwait(false); } - internal static async void ReadAsync( + internal static async Task ReadAsync( Stream stream, bool unmask, Action completed, Action error) { try { var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); if (header.Length != 2) + { throw new WebSocketException( "The header part of a frame cannot be read from the data source."); + } - var frame = read(header, stream, unmask); - if (completed != null) - completed(frame); + var frame = await ReadAsync(header, stream, unmask).ConfigureAwait(false); + completed?.Invoke(frame); } catch (Exception ex) { - if (error != null) - { - error(ex); - } + error.Invoke(ex); } } diff --git a/deployment/debian-x64/build.sh b/deployment/debian-x64/build.sh deleted file mode 100755 index 47cfb5327..000000000 --- a/deployment/debian-x64/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -source ../common.build.sh - -VERSION=`get_version ../..` - -build_jellyfin ../../Jellyfin.Server Release debian-x64 `pwd`/dist/jellyfin_${VERSION} diff --git a/deployment/osx-x64/build.sh b/deployment/macos/build.sh similarity index 100% rename from deployment/osx-x64/build.sh rename to deployment/macos/build.sh diff --git a/deployment/debian-x64/clean.sh b/deployment/macos/clean.sh similarity index 100% rename from deployment/debian-x64/clean.sh rename to deployment/macos/clean.sh diff --git a/deployment/debian-x64/dependencies.txt b/deployment/macos/dependencies.txt similarity index 100% rename from deployment/debian-x64/dependencies.txt rename to deployment/macos/dependencies.txt diff --git a/deployment/debian-x64/package.sh b/deployment/macos/package.sh similarity index 100% rename from deployment/debian-x64/package.sh rename to deployment/macos/package.sh diff --git a/deployment/osx-x64/clean.sh b/deployment/osx-x64/clean.sh deleted file mode 100755 index 3df2d7796..000000000 --- a/deployment/osx-x64/clean.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -source ../common.build.sh - -VERSION=`get_version ../..` - -clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION} diff --git a/deployment/osx-x64/package.sh b/deployment/osx-x64/package.sh deleted file mode 100755 index 13b943ea8..000000000 --- a/deployment/osx-x64/package.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -source ../common.build.sh - -VERSION=`get_version ../..` - -package_portable ../.. `pwd`/dist/jellyfin_${VERSION} diff --git a/deployment/framework/build.sh b/deployment/portable/build.sh similarity index 100% rename from deployment/framework/build.sh rename to deployment/portable/build.sh diff --git a/deployment/framework/clean.sh b/deployment/portable/clean.sh similarity index 100% rename from deployment/framework/clean.sh rename to deployment/portable/clean.sh diff --git a/deployment/framework/package.sh b/deployment/portable/package.sh similarity index 100% rename from deployment/framework/package.sh rename to deployment/portable/package.sh diff --git a/deployment/ubuntu-x64/build.sh b/deployment/ubuntu-x64/build.sh deleted file mode 100755 index 870bac780..000000000 --- a/deployment/ubuntu-x64/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -source ../common.build.sh - -VERSION=`get_version ../..` - -build_jellyfin ../../Jellyfin.Server Release ubuntu-x64 `pwd`/dist/jellyfin_${VERSION} diff --git a/deployment/ubuntu-x64/clean.sh b/deployment/ubuntu-x64/clean.sh deleted file mode 100755 index 3df2d7796..000000000 --- a/deployment/ubuntu-x64/clean.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -source ../common.build.sh - -VERSION=`get_version ../..` - -clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION} diff --git a/deployment/ubuntu-x64/dependencies.txt b/deployment/ubuntu-x64/dependencies.txt deleted file mode 100644 index 3d25d1bdf..000000000 --- a/deployment/ubuntu-x64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -dotnet diff --git a/deployment/ubuntu-x64/package.sh b/deployment/ubuntu-x64/package.sh deleted file mode 100755 index 13b943ea8..000000000 --- a/deployment/ubuntu-x64/package.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -source ../common.build.sh - -VERSION=`get_version ../..` - -package_portable ../.. `pwd`/dist/jellyfin_${VERSION} diff --git a/deployment/win-generic/dependencies.txt b/deployment/win-generic/dependencies.txt deleted file mode 100644 index 3d25d1bdf..000000000 --- a/deployment/win-generic/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -dotnet diff --git a/deployment/win-generic/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1 similarity index 100% rename from deployment/win-generic/build-jellyfin.ps1 rename to deployment/windows/build-jellyfin.ps1 diff --git a/deployment/osx-x64/dependencies.txt b/deployment/windows/dependencies.txt similarity index 100% rename from deployment/osx-x64/dependencies.txt rename to deployment/windows/dependencies.txt diff --git a/deployment/win-generic/install-jellyfin.ps1 b/deployment/windows/install-jellyfin.ps1 similarity index 100% rename from deployment/win-generic/install-jellyfin.ps1 rename to deployment/windows/install-jellyfin.ps1 diff --git a/deployment/win-generic/install.bat b/deployment/windows/install.bat similarity index 100% rename from deployment/win-generic/install.bat rename to deployment/windows/install.bat