Merge branch 'master' into populate-extras

This commit is contained in:
Liggy 2019-02-20 14:24:23 +01:00 committed by Torsten
commit 33171a58b5
69 changed files with 925 additions and 1782 deletions

View File

@ -20,6 +20,7 @@
- [RazeLighter777](https://github.com/RazeLighter777) - [RazeLighter777](https://github.com/RazeLighter777)
- [WillWill56](https://github.com/WillWill56) - [WillWill56](https://github.com/WillWill56)
- [Liggy](https://github.com/Liggy) - [Liggy](https://github.com/Liggy)
- [fruhnow](https://github.com/fruhnow)
# Emby Contributors # Emby Contributors

View File

@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Server; using Emby.Dlna.Server;
@ -733,26 +734,21 @@ namespace Emby.Dlna.PlayTo
return (true, null); return (true, null);
} }
XElement uPnpResponse; XElement uPnpResponse = null;
// Handle different variations sent back by devices
try try
{ {
uPnpResponse = XElement.Parse(trackString); uPnpResponse = ParseResponse(trackString);
} }
catch (Exception) catch (Exception ex)
{ {
// first try to add a root node with a dlna namesapce _logger.LogError(ex, "Uncaught exception while parsing xml");
try }
{
uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>"); if (uPnpResponse == null)
uPnpResponse = uPnpResponse.Descendants().First(); {
} _logger.LogError("Failed to parse xml: \n {Xml}", trackString);
catch (Exception ex) return (true, null);
{
_logger.LogError(ex, "Unable to parse xml {0}", trackString);
return (true, null);
}
} }
var e = uPnpResponse.Element(uPnpNamespaces.items); var e = uPnpResponse.Element(uPnpNamespaces.items);
@ -762,6 +758,43 @@ namespace Emby.Dlna.PlayTo
return (true, uTrack); return (true, uTrack);
} }
private XElement ParseResponse(string xml)
{
// Handle different variations sent back by devices
try
{
return XElement.Parse(xml);
}
catch (XmlException)
{
}
// first try to add a root node with a dlna namesapce
try
{
return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>")
.Descendants()
.First();
}
catch (XmlException)
{
}
// some devices send back invalid xml
try
{
return XElement.Parse(xml.Replace("&", "&amp;"));
}
catch (XmlException)
{
}
return null;
}
private static uBaseObject CreateUBaseObject(XElement container, string trackUri) private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
{ {
if (container == null) if (container == null)

View File

@ -11,101 +11,81 @@ namespace Emby.Notifications
public class CoreNotificationTypes : INotificationTypeFactory public class CoreNotificationTypes : INotificationTypeFactory
{ {
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IServerApplicationHost _appHost;
public CoreNotificationTypes(ILocalizationManager localization, IServerApplicationHost appHost) public CoreNotificationTypes(ILocalizationManager localization)
{ {
_localization = localization; _localization = localization;
_appHost = appHost;
} }
public IEnumerable<NotificationTypeInfo> GetNotificationTypes() public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
{ {
var knownTypes = new List<NotificationTypeInfo> var knownTypes = new NotificationTypeInfo[]
{ {
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.ApplicationUpdateInstalled.ToString() Type = NotificationType.ApplicationUpdateInstalled.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.InstallationFailed.ToString() Type = NotificationType.InstallationFailed.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.PluginInstalled.ToString() Type = NotificationType.PluginInstalled.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.PluginError.ToString() Type = NotificationType.PluginError.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.PluginUninstalled.ToString() Type = NotificationType.PluginUninstalled.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.PluginUpdateInstalled.ToString() Type = NotificationType.PluginUpdateInstalled.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.ServerRestartRequired.ToString() Type = NotificationType.ServerRestartRequired.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.TaskFailed.ToString() Type = NotificationType.TaskFailed.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.NewLibraryContent.ToString() Type = NotificationType.NewLibraryContent.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.AudioPlayback.ToString() Type = NotificationType.AudioPlayback.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.VideoPlayback.ToString() Type = NotificationType.VideoPlayback.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.AudioPlaybackStopped.ToString() Type = NotificationType.AudioPlaybackStopped.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.VideoPlaybackStopped.ToString() Type = NotificationType.VideoPlaybackStopped.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.CameraImageUploaded.ToString() Type = NotificationType.CameraImageUploaded.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.UserLockedOut.ToString() Type = NotificationType.UserLockedOut.ToString()
} },
}; new NotificationTypeInfo
if (!_appHost.CanSelfUpdate)
{
knownTypes.Add(new NotificationTypeInfo
{ {
Type = NotificationType.ApplicationUpdateAvailable.ToString() Type = NotificationType.ApplicationUpdateAvailable.ToString()
}); }
} };
foreach (var type in knownTypes) foreach (var type in knownTypes)
{ {

View File

@ -5,21 +5,17 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Notifications; using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Notifications namespace Emby.Notifications
@ -29,43 +25,40 @@ namespace Emby.Notifications
/// </summary> /// </summary>
public class Notifications : IServerEntryPoint public class Notifications : IServerEntryPoint
{ {
private readonly IInstallationManager _installationManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ITaskManager _taskManager;
private readonly INotificationManager _notificationManager; private readonly INotificationManager _notificationManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ISessionManager _sessionManager;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private Timer LibraryUpdateTimer { get; set; } private Timer LibraryUpdateTimer { get; set; }
private readonly object _libraryChangedSyncLock = new object(); private readonly object _libraryChangedSyncLock = new object();
private readonly IConfigurationManager _config; private readonly IConfigurationManager _config;
private readonly IDeviceManager _deviceManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IActivityManager _activityManager; private readonly IActivityManager _activityManager;
private string[] _coreNotificationTypes; 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; _logger = logger;
_taskManager = taskManager;
_notificationManager = notificationManager; _notificationManager = notificationManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_sessionManager = sessionManager;
_appHost = appHost; _appHost = appHost;
_config = config; _config = config;
_deviceManager = deviceManager;
_localization = localization; _localization = localization;
_activityManager = activityManager; _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() public Task RunAsync()
@ -124,10 +117,9 @@ namespace Emby.Notifications
return _config.GetConfiguration<NotificationOptions>("notifications"); return _config.GetConfiguration<NotificationOptions>("notifications");
} }
async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e) private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
{ {
// This notification is for users who can't auto-update (aka running as service) if (!_appHost.HasUpdateAvailable)
if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate)
{ {
return; return;
} }
@ -145,7 +137,7 @@ namespace Emby.Notifications
} }
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>(); private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
{ {
if (!FilterItem(e.Item)) if (!FilterItem(e.Item))
{ {

View File

@ -72,12 +72,6 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The plugin configurations path.</value> /// <value>The plugin configurations path.</value>
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations"); public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
/// <summary>
/// Gets the path to where temporary update files will be stored
/// </summary>
/// <value>The plugin configurations path.</value>
public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates");
/// <summary> /// <summary>
/// Gets the path to the log directory /// Gets the path to the log directory
/// </summary> /// </summary>

View File

@ -123,12 +123,6 @@ namespace Emby.Server.Implementations
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
public abstract bool CanSelfRestart { get; } public abstract bool CanSelfRestart { get; }
/// <summary>
/// Gets or sets a value indicating whether this instance can self update.
/// </summary>
/// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
public virtual bool CanSelfUpdate => false;
public virtual bool CanLaunchWebBrowser public virtual bool CanLaunchWebBrowser
{ {
get get
@ -1456,7 +1450,6 @@ namespace Emby.Server.Implementations
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName, OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
CanSelfRestart = CanSelfRestart, CanSelfRestart = CanSelfRestart,
CanSelfUpdate = CanSelfUpdate,
CanLaunchWebBrowser = CanLaunchWebBrowser, CanLaunchWebBrowser = CanLaunchWebBrowser,
WanAddress = wanAddress, WanAddress = wanAddress,
HasUpdateAvailable = HasUpdateAvailable, HasUpdateAvailable = HasUpdateAvailable,
@ -1755,21 +1748,6 @@ namespace Emby.Server.Implementations
Plugins = list.ToArray(); Plugins = list.ToArray();
} }
/// <summary>
/// Updates the application.
/// </summary>
/// <param name="package">The package that contains the update</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress)
{
await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false);
HasUpdateAvailable = false;
OnApplicationUpdated(package);
}
/// <summary> /// <summary>
/// This returns localhost in the case of no external dns, and the hostname if the /// This returns localhost in the case of no external dns, and the hostname if the
/// dns is prefixed with a valid Uri prefix. /// dns is prefixed with a valid Uri prefix.

View File

@ -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()); Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
} }
@ -232,23 +232,6 @@ namespace Emby.Server.Implementations.Data
protected virtual int? CacheSize => null; 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; private bool _disposed;
protected void CheckDisposed() 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) public static IDisposable Read(this ReaderWriterLockSlim obj)
{ {
//if (BaseSqliteRepository.ThreadSafeMode > 0) //if (BaseSqliteRepository.ThreadSafeMode > 0)
@ -390,6 +366,7 @@ namespace Emby.Server.Implementations.Data
//} //}
return new WriteLockToken(obj); return new WriteLockToken(obj);
} }
public static IDisposable Write(this ReaderWriterLockSlim obj) public static IDisposable Write(this ReaderWriterLockSlim obj)
{ {
//if (BaseSqliteRepository.ThreadSafeMode > 0) //if (BaseSqliteRepository.ThreadSafeMode > 0)

View File

@ -536,7 +536,7 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(item)); throw new ArgumentNullException(nameof(item));
} }
SaveItems(new List<BaseItem> { item }, cancellationToken); SaveItems(new [] { item }, cancellationToken);
} }
public void SaveImages(BaseItem item) public void SaveImages(BaseItem item)
@ -576,7 +576,7 @@ namespace Emby.Server.Implementations.Data
/// or /// or
/// cancellationToken /// cancellationToken
/// </exception> /// </exception>
public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken) public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
{ {
if (items == null) if (items == null)
{ {
@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.Data
CheckDisposed(); CheckDisposed();
var tuples = new List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>>(); var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>();
foreach (var item in items) foreach (var item in items)
{ {
var ancestorIds = item.SupportsAncestors ? var ancestorIds = item.SupportsAncestors ?
@ -599,7 +599,7 @@ namespace Emby.Server.Implementations.Data
var userdataKey = item.GetUserDataKeys().FirstOrDefault(); var userdataKey = item.GetUserDataKeys().FirstOrDefault();
var inheritedTags = item.GetInheritedTags(); var inheritedTags = item.GetInheritedTags();
tuples.Add(new Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>(item, ancestorIds, topParent, userdataKey, inheritedTags)); tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
} }
using (WriteLock.Write()) using (WriteLock.Write())
@ -615,7 +615,7 @@ namespace Emby.Server.Implementations.Data
} }
} }
private void SaveItemsInTranscation(IDatabaseConnection db, List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>> tuples) private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples)
{ {
var statements = PrepareAllSafe(db, new string[] var statements = PrepareAllSafe(db, new string[]
{ {
@ -966,7 +966,7 @@ namespace Emby.Server.Implementations.Data
if (item.ExtraIds.Length > 0) if (item.ExtraIds.Length > 0)
{ {
saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray())); saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds));
} }
else else
{ {
@ -1183,9 +1183,9 @@ namespace Emby.Server.Implementations.Data
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
public BaseItem RetrieveItem(Guid id) 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(); CheckDisposed();
@ -2079,14 +2079,14 @@ namespace Emby.Server.Implementations.Data
return false; return false;
} }
var sortingFields = query.OrderBy.Select(i => i.Item1); var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase);
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase) return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|| sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.IsPlayed)
|| sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.IsUnplayed)
|| sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.PlayCount)
|| sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.DatePlayed)
|| sortingFields.Contains(ItemSortBy.SeriesDatePlayed, StringComparer.OrdinalIgnoreCase) || sortingFields.Contains(ItemSortBy.SeriesDatePlayed)
|| query.IsFavoriteOrLiked.HasValue || query.IsFavoriteOrLiked.HasValue
|| query.IsFavorite.HasValue || query.IsFavorite.HasValue
|| query.IsResumable.HasValue || query.IsResumable.HasValue
@ -2094,9 +2094,9 @@ namespace Emby.Server.Implementations.Data
|| query.IsLiked.HasValue; || query.IsLiked.HasValue;
} }
private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields)) private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields))
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
.ToList(); .ToArray();
private string[] GetColumnNamesFromField(ItemFields field) private string[] GetColumnNamesFromField(ItemFields field)
{ {
@ -2151,18 +2151,26 @@ namespace Emby.Server.Implementations.Data
} }
} }
private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Series",
"Season",
"MusicAlbum",
"MusicArtist",
"PhotoAlbum"
};
private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Program",
"TvChannel",
"LiveTvProgram",
"LiveTvTvChannel"
};
private bool HasProgramAttributes(InternalItemsQuery query) private bool HasProgramAttributes(InternalItemsQuery query)
{ {
var excludeParentTypes = new string[] if (_programExcludeParentTypes.Contains(query.ParentType))
{
"Series",
"Season",
"MusicAlbum",
"MusicArtist",
"PhotoAlbum"
};
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@ -2172,29 +2180,18 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _programTypes.Contains(x));
{
"Program",
"TvChannel",
"LiveTvProgram",
"LiveTvTvChannel"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"TvChannel",
"LiveTvTvChannel"
};
private bool HasServiceName(InternalItemsQuery query) private bool HasServiceName(InternalItemsQuery query)
{ {
var excludeParentTypes = new string[] if (_programExcludeParentTypes.Contains(query.ParentType))
{
"Series",
"Season",
"MusicAlbum",
"MusicArtist",
"PhotoAlbum"
};
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@ -2204,27 +2201,18 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x));
{
"TvChannel",
"LiveTvTvChannel"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Program",
"LiveTvProgram"
};
private bool HasStartDate(InternalItemsQuery query) private bool HasStartDate(InternalItemsQuery query)
{ {
var excludeParentTypes = new string[] if (_programExcludeParentTypes.Contains(query.ParentType))
{
"Series",
"Season",
"MusicAlbum",
"MusicArtist",
"PhotoAlbum"
};
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@ -2234,13 +2222,7 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x));
{
"Program",
"LiveTvProgram"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private bool HasEpisodeAttributes(InternalItemsQuery query) private bool HasEpisodeAttributes(InternalItemsQuery query)
@ -2263,16 +2245,26 @@ namespace Emby.Server.Implementations.Data
return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase); return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase);
} }
private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Series",
"Season",
"PhotoAlbum"
};
private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Audio",
"MusicAlbum",
"MusicVideo",
"AudioBook",
"AudioPodcast"
};
private bool HasArtistFields(InternalItemsQuery query) private bool HasArtistFields(InternalItemsQuery query)
{ {
var excludeParentTypes = new string[] if (_artistExcludeParentTypes.Contains(query.ParentType))
{
"Series",
"Season",
"PhotoAlbum"
};
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@ -2282,18 +2274,18 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x));
{
"Audio",
"MusicAlbum",
"MusicVideo",
"AudioBook",
"AudioPodcast"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Audio",
"MusicAlbum",
"MusicVideo",
"AudioBook",
"AudioPodcast"
};
private bool HasSeriesFields(InternalItemsQuery query) private bool HasSeriesFields(InternalItemsQuery query)
{ {
if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase)) if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase))
@ -2306,26 +2298,18 @@ namespace Emby.Server.Implementations.Data
return true; return true;
} }
var types = new string[] return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
{
"Book",
"AudioBook",
"Episode",
"Season"
};
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
} }
private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns) private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns)
{ {
var list = startColumns.ToList(); var list = startColumns.ToList();
foreach (var field in allFields) foreach (var field in _allFields)
{ {
if (!HasField(query, field)) if (!HasField(query, field))
{ {
foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList()) foreach (var fieldToRemove in GetColumnNamesFromField(field))
{ {
list.Remove(fieldToRemove); list.Remove(fieldToRemove);
} }
@ -2419,11 +2403,14 @@ namespace Emby.Server.Implementations.Data
list.Add(builder.ToString()); list.Add(builder.ToString());
var excludeIds = query.ExcludeItemIds.ToList(); var oldLen = query.ExcludeItemIds.Length;
excludeIds.Add(item.Id); var newLen = oldLen + item.ExtraIds.Length + 1;
excludeIds.AddRange(item.ExtraIds); 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; query.ExcludeProviderIds = item.ProviderIds;
} }
@ -2444,7 +2431,7 @@ namespace Emby.Server.Implementations.Data
list.Add(builder.ToString()); list.Add(builder.ToString());
} }
return list.ToArray(); return list;
} }
private void BindSearchParams(InternalItemsQuery query, IStatement statement) private void BindSearchParams(InternalItemsQuery query, IStatement statement)
@ -2723,18 +2710,17 @@ namespace Emby.Server.Implementations.Data
private void AddItem(List<BaseItem> items, BaseItem newItem) private void AddItem(List<BaseItem> items, BaseItem newItem)
{ {
var providerIds = newItem.ProviderIds.ToList();
for (var i = 0; i < items.Count; i++) for (var i = 0; i < items.Count; i++)
{ {
var item = items[i]; var item = items[i];
foreach (var providerId in providerIds) foreach (var providerId in newItem.ProviderIds)
{ {
if (providerId.Key == MetadataProviders.TmdbCollection.ToString()) if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
{ {
continue; continue;
} }
if (item.GetProviderId(providerId.Key) == providerId.Value) if (item.GetProviderId(providerId.Key) == providerId.Value)
{ {
if (newItem.SourceType == SourceType.Library) if (newItem.SourceType == SourceType.Library)
@ -2753,10 +2739,10 @@ namespace Emby.Server.Implementations.Data
{ {
var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds; var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
int slowThreshold = 1000; int slowThreshold = 100;
#if DEBUG #if DEBUG
slowThreshold = 250; slowThreshold = 10;
#endif #endif
if (elapsed >= slowThreshold) if (elapsed >= slowThreshold)
@ -2806,7 +2792,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ? var whereText = whereClauses.Count == 0 ?
string.Empty : string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray()); " where " + string.Join(" AND ", whereClauses);
commandText += whereText commandText += whereText
+ GetGroupBy(query) + GetGroupBy(query)
@ -2930,25 +2916,31 @@ namespace Emby.Server.Implementations.Data
private string GetOrderByText(InternalItemsQuery query) private string GetOrderByText(InternalItemsQuery query)
{ {
var orderBy = query.OrderBy.ToList(); if (string.IsNullOrEmpty(query.SearchTerm))
var enableOrderInversion = false;
if (query.SimilarTo != null && orderBy.Count == 0)
{ {
orderBy.Add(new ValueTuple<string, SortOrder>("SimilarityScore", SortOrder.Descending)); int oldLen = query.OrderBy.Length;
orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
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)) var orderBy = query.OrderBy;
{
orderBy = new List<(string, SortOrder)>();
orderBy.Add(new ValueTuple<string, SortOrder>("SearchScore", SortOrder.Descending));
orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
}
query.OrderBy = orderBy.ToArray(); if (orderBy.Length == 0)
if (orderBy.Count == 0)
{ {
return string.Empty; return string.Empty;
} }
@ -2957,6 +2949,7 @@ namespace Emby.Server.Implementations.Data
{ {
var columnMap = MapOrderByField(i.Item1, query); var columnMap = MapOrderByField(i.Item1, query);
var columnAscending = i.Item2 == SortOrder.Ascending; var columnAscending = i.Item2 == SortOrder.Ascending;
const bool enableOrderInversion = false;
if (columnMap.Item2 && enableOrderInversion) if (columnMap.Item2 && enableOrderInversion)
{ {
columnAscending = !columnAscending; columnAscending = !columnAscending;
@ -2968,7 +2961,7 @@ namespace Emby.Server.Implementations.Data
})); }));
} }
private ValueTuple<string, bool> MapOrderByField(string name, InternalItemsQuery query) private (string, bool) MapOrderByField(string name, InternalItemsQuery query)
{ {
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase)) if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{ {
@ -3218,7 +3211,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ? var whereText = whereClauses.Count == 0 ?
string.Empty : string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray()); " where " + string.Join(" AND ", whereClauses);
commandText += whereText commandText += whereText
+ GetGroupBy(query) + GetGroupBy(query)
@ -4378,7 +4371,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (query.Years.Length > 1) else if (query.Years.Length > 1)
{ {
var val = string.Join(",", query.Years.ToArray()); var val = string.Join(",", query.Years);
whereClauses.Add("ProductionYear in (" + val + ")"); whereClauses.Add("ProductionYear in (" + val + ")");
} }
@ -4952,7 +4945,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return result; return result;
} }
return new[] { value }.Where(IsValidType); if (IsValidType(value))
{
return new[] { value };
}
return Array.Empty<string>();
} }
public void DeleteItem(Guid id, CancellationToken cancellationToken) public void DeleteItem(Guid id, CancellationToken cancellationToken)
@ -5215,32 +5213,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
} }
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
{ {
return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName); return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
{ {
return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName); return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{ {
return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName); return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
{ {
return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName); return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
{ {
return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{ {
return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName);
} }
@ -5317,7 +5315,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
} }
} }
private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
{ {
if (query == null) if (query == null)
{ {
@ -5335,7 +5333,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
var typeClause = itemValueTypes.Length == 1 ? var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : ("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; 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 + ")"); whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
var typeWhereText = whereClauses.Count == 0 ? itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses);
string.Empty :
" where " + string.Join(" AND ", whereClauses);
itemCountColumnQuery += typeWhereText;
itemCountColumns = new Dictionary<string, string>() itemCountColumns = new Dictionary<string, string>()
{ {
@ -5400,7 +5394,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
IsSeries = query.IsSeries IsSeries = query.IsSeries
}; };
columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList(); columns = GetFinalColumnsToSelect(query, columns);
var commandText = "select " var commandText = "select "
+ string.Join(",", columns) + string.Join(",", columns)
@ -5492,8 +5486,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{ {
return connection.RunInTransaction(db => return connection.RunInTransaction(db =>
{ {
var list = new List<Tuple<BaseItem, ItemCounts>>(); var list = new List<(BaseItem, ItemCounts)>();
var result = new QueryResult<Tuple<BaseItem, ItemCounts>>(); var result = new QueryResult<(BaseItem, ItemCounts)>();
var statements = PrepareAllSafe(db, statementTexts); 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; var countStartColumn = columns.Count - 1;
list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount))); list.Add((item, GetItemCounts(row, countStartColumn, typesToCount)));
} }
} }
@ -6198,6 +6192,5 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return item; return item;
} }
} }
} }

View File

@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary> /// </summary>
private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null) private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
{ {
var result = new StreamWriter(content, contentType, _logger); var result = new StreamWriter(content, contentType);
if (responseHeaders == null) if (responseHeaders == null)
{ {
@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer
content = Array.Empty<byte>(); content = Array.Empty<byte>();
} }
result = new StreamWriter(content, contentType, contentLength, _logger); result = new StreamWriter(content, contentType, contentLength);
} }
else else
{ {
@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>(); responseHeaders = new Dictionary<string, string>();
} }
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
{ {
responseHeaders["Expires"] = "-1"; responseHeaders["Expires"] = "-1";
} }
@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer
bytes = Array.Empty<byte>(); bytes = Array.Empty<byte>();
} }
result = new StreamWriter(bytes, contentType, contentLength, _logger); result = new StreamWriter(bytes, contentType, contentLength);
} }
else else
{ {
@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>(); responseHeaders = new Dictionary<string, string>();
} }
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _))
{ {
responseHeaders["Expires"] = "-1"; responseHeaders["Expires"] = "-1";
} }
@ -277,9 +277,10 @@ namespace Emby.Server.Implementations.HttpServer
private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null) private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null)
{ {
var contentType = request.ResponseContentType; // TODO: @bond use Span and .Equals
var contentType = request.ResponseContentType?.Split(';')[0].Trim().ToLowerInvariant();
switch (GetRealContentType(contentType)) switch (contentType)
{ {
case "application/xml": case "application/xml":
case "text/xml": case "text/xml":
@ -333,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer
if (isHeadRequest) if (isHeadRequest)
{ {
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger); var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength);
AddResponseHeaders(result, responseHeaders); AddResponseHeaders(result, responseHeaders);
return result; return result;
} }
else else
{ {
var result = new StreamWriter(content, contentType, contentLength, _logger); var result = new StreamWriter(content, contentType, contentLength);
AddResponseHeaders(result, responseHeaders); AddResponseHeaders(result, responseHeaders);
return result; return result;
} }
@ -348,13 +349,19 @@ namespace Emby.Server.Implementations.HttpServer
private byte[] Compress(byte[] bytes, string compressionType) private byte[] Compress(byte[] bytes, string compressionType)
{ {
if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase)) if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
{
return CompressBrotli(bytes); return CompressBrotli(bytes);
}
if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase)) if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
{
return Deflate(bytes); return Deflate(bytes);
}
if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase)) if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
{
return GZip(bytes); return GZip(bytes);
}
throw new NotSupportedException(compressionType); 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) private static string SerializeToXmlString(object from)
{ {
using (var ms = new MemoryStream()) 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, OnComplete = options.OnComplete,
OnError = options.OnError OnError = options.OnError

View File

@ -14,8 +14,6 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary> /// </summary>
public class StreamWriter : IAsyncStreamWriter, IHasHeaders public class StreamWriter : IAsyncStreamWriter, IHasHeaders
{ {
private ILogger Logger { get; set; }
private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary> /// <summary>
@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param> /// <param name="contentType">Type of the content.</param>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
public StreamWriter(Stream source, string contentType, ILogger logger) public StreamWriter(Stream source, string contentType)
{ {
if (string.IsNullOrEmpty(contentType)) if (string.IsNullOrEmpty(contentType))
{ {
@ -53,7 +51,6 @@ namespace Emby.Server.Implementations.HttpServer
} }
SourceStream = source; SourceStream = source;
Logger = logger;
Headers["Content-Type"] = contentType; Headers["Content-Type"] = contentType;
@ -69,7 +66,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param> /// <param name="contentType">Type of the content.</param>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
public StreamWriter(byte[] source, string contentType, int contentLength, ILogger logger) public StreamWriter(byte[] source, string contentType, int contentLength)
{ {
if (string.IsNullOrEmpty(contentType)) if (string.IsNullOrEmpty(contentType))
{ {
@ -77,7 +74,6 @@ namespace Emby.Server.Implementations.HttpServer
} }
SourceBytes = source; SourceBytes = source;
Logger = logger;
Headers["Content-Type"] = contentType; Headers["Content-Type"] = contentType;

View File

@ -1225,9 +1225,9 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException">id</exception> /// <exception cref="ArgumentNullException">id</exception>
public BaseItem GetItemById(Guid 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)) if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
@ -1237,8 +1237,6 @@ namespace Emby.Server.Implementations.Library
item = RetrieveItem(id); item = RetrieveItem(id);
//_logger.LogDebug("GetitemById {0}", id);
if (item != null) if (item != null)
{ {
RegisterItem(item); RegisterItem(item);
@ -1333,7 +1331,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetItemIdsList(query); return ItemRepository.GetItemIdsList(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1344,7 +1342,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetStudios(query); return ItemRepository.GetStudios(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1355,7 +1353,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetGenres(query); return ItemRepository.GetGenres(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1366,7 +1364,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetMusicGenres(query); return ItemRepository.GetMusicGenres(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1377,7 +1375,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetAllArtists(query); return ItemRepository.GetAllArtists(query);
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1421,7 +1419,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query) public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@ -1808,18 +1806,16 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns> /// <returns>Task.</returns>
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken) public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
{ {
var list = items.ToList(); ItemRepository.SaveItems(items, cancellationToken);
ItemRepository.SaveItems(list, cancellationToken); foreach (var item in items)
foreach (var item in list)
{ {
RegisterItem(item); RegisterItem(item);
} }
if (ItemAdded != null) if (ItemAdded != null)
{ {
foreach (var item in list) foreach (var item in items)
{ {
// With the live tv guide this just creates too much noise // With the live tv guide this just creates too much noise
if (item.SourceType != SourceType.Library) if (item.SourceType != SourceType.Library)
@ -1853,7 +1849,7 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// Updates the item. /// Updates the item.
/// </summary> /// </summary>
public void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{ {
foreach (var item in items) foreach (var item in items)
{ {
@ -1908,7 +1904,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns> /// <returns>Task.</returns>
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{ {
UpdateItems(new List<BaseItem> { item }, parent, updateReason, cancellationToken); UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
} }
/// <summary> /// <summary>
@ -2005,9 +2001,7 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault(); .FirstOrDefault();
} }
var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
return options;
} }
public string GetContentType(BaseItem item) public string GetContentType(BaseItem item)
@ -2017,11 +2011,13 @@ namespace Emby.Server.Implementations.Library
{ {
return configuredContentType; return configuredContentType;
} }
configuredContentType = GetConfiguredContentType(item, true); configuredContentType = GetConfiguredContentType(item, true);
if (!string.IsNullOrEmpty(configuredContentType)) if (!string.IsNullOrEmpty(configuredContentType))
{ {
return configuredContentType; return configuredContentType;
} }
return GetInheritedContentType(item); return GetInheritedContentType(item);
} }
@ -2056,6 +2052,7 @@ namespace Emby.Server.Implementations.Library
{ {
return collectionFolder.CollectionType; return collectionFolder.CollectionType;
} }
return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath); return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath);
} }
@ -2066,6 +2063,7 @@ namespace Emby.Server.Implementations.Library
{ {
return nameValuePair.Value; return nameValuePair.Value;
} }
return null; return null;
} }
@ -2108,9 +2106,9 @@ namespace Emby.Server.Implementations.Library
string viewType, string viewType,
string sortName) string sortName)
{ {
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views"); var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath,
"views",
path = Path.Combine(path, _fileSystem.GetValidFilename(viewType)); _fileSystem.GetValidFilename(viewType));
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView)); var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));

View File

@ -168,9 +168,9 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public User GetUserById(Guid id) 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); return Users.FirstOrDefault(u => u.Id == id);

View File

@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.LiveTv
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{ {
var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId); var user = query.UserId == Guid.Empty ? null : _userManager.GetUserById(query.UserId);
var topFolder = GetInternalLiveTvFolder(cancellationToken); var topFolder = GetInternalLiveTvFolder(cancellationToken);

View File

@ -41,6 +41,27 @@ namespace Emby.Server.Implementations.Serialization
ServiceStack.Text.JsonSerializer.SerializeToStream(obj, obj.GetType(), stream); ServiceStack.Text.JsonSerializer.SerializeToStream(obj, obj.GetType(), stream);
} }
/// <summary>
/// Serializes to stream.
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="stream">The stream.</param>
/// <exception cref="ArgumentNullException">obj</exception>
public void SerializeToStream<T>(T obj, Stream stream)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
ServiceStack.Text.JsonSerializer.SerializeToStream<T>(obj, stream);
}
/// <summary> /// <summary>
/// Serializes to file. /// Serializes to file.
/// </summary> /// </summary>

View File

@ -187,26 +187,13 @@ namespace Jellyfin.Server
if (string.IsNullOrEmpty(dataDir)) if (string.IsNullOrEmpty(dataDir))
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) // LocalApplicationData follows the XDG spec on unix machines
{ dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "jellyfin");
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");
} }
} }
Directory.CreateDirectory(dataDir);
// configDir // configDir
// IF --configdir // IF --configdir
// ELSE IF $JELLYFIN_CONFIG_DIR // ELSE IF $JELLYFIN_CONFIG_DIR
@ -216,7 +203,6 @@ namespace Jellyfin.Server
// ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin // ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
// ELSE $HOME/.config/jellyfin // ELSE $HOME/.config/jellyfin
var configDir = options.ConfigDir; var configDir = options.ConfigDir;
if (string.IsNullOrEmpty(configDir)) if (string.IsNullOrEmpty(configDir))
{ {
configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR"); configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
@ -300,7 +286,6 @@ namespace Jellyfin.Server
// Ensure the main folders exist before we continue // Ensure the main folders exist before we continue
try try
{ {
Directory.CreateDirectory(dataDir);
Directory.CreateDirectory(logDir); Directory.CreateDirectory(logDir);
Directory.CreateDirectory(configDir); Directory.CreateDirectory(configDir);
Directory.CreateDirectory(cacheDir); Directory.CreateDirectory(cacheDir);

View File

@ -6,9 +6,13 @@ namespace Jellyfin.Server.SocketSharp
public class HttpFile : IHttpFile public class HttpFile : IHttpFile
{ {
public string Name { get; set; } public string Name { get; set; }
public string FileName { get; set; } public string FileName { get; set; }
public long ContentLength { get; set; } public long ContentLength { get; set; }
public string ContentType { get; set; } public string ContentType { get; set; }
public Stream InputStream { get; set; } public Stream InputStream { get; set; }
} }
} }

View File

@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
public sealed class HttpPostedFile : IDisposable
{
private string _name;
private string _contentType;
private Stream _stream;
private bool _disposed = false;
internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length)
{
_name = name;
_contentType = content_type;
_stream = new ReadSubStream(base_stream, offset, length);
}
public string ContentType => _contentType;
public int ContentLength => (int)_stream.Length;
public string FileName => _name;
public Stream InputStream => _stream;
/// <summary>
/// Releases the unmanaged resources and disposes of the managed resources used.
/// </summary>
public void Dispose()
{
if (_disposed)
{
return;
}
_stream.Dispose();
_stream = null;
_name = null;
_contentType = null;
_disposed = true;
}
private class ReadSubStream : Stream
{
private Stream _stream;
private long _offset;
private long _end;
private long _position;
public ReadSubStream(Stream s, long offset, long length)
{
_stream = s;
_offset = offset;
_end = offset + length;
_position = offset;
}
public override void Flush()
{
}
public override int Read(byte[] buffer, int dest_offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (dest_offset < 0)
{
throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0");
}
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "< 0");
}
int len = buffer.Length;
if (dest_offset > len)
{
throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset));
}
// reordered to avoid possible integer overflow
if (dest_offset > len - count)
{
throw new ArgumentException("Reading would overrun buffer", nameof(count));
}
if (count > _end - _position)
{
count = (int)(_end - _position);
}
if (count <= 0)
{
return 0;
}
_stream.Position = _position;
int result = _stream.Read(buffer, dest_offset, count);
if (result > 0)
{
_position += result;
}
else
{
_position = _end;
}
return result;
}
public override int ReadByte()
{
if (_position >= _end)
{
return -1;
}
_stream.Position = _position;
int result = _stream.ReadByte();
if (result < 0)
{
_position = _end;
}
else
{
_position++;
}
return result;
}
public override long Seek(long d, SeekOrigin origin)
{
long real;
switch (origin)
{
case SeekOrigin.Begin:
real = _offset + d;
break;
case SeekOrigin.End:
real = _end + d;
break;
case SeekOrigin.Current:
real = _position + d;
break;
default:
throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
}
long virt = real - _offset;
if (virt < 0 || virt > Length)
{
throw new ArgumentException("Invalid position", nameof(d));
}
_position = _stream.Seek(real, SeekOrigin.Begin);
return _position;
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
public override long Length => _end - _offset;
public override long Position
{
get => _position - _offset;
set
{
if (value > Length)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_position = Seek(value, SeekOrigin.Begin);
}
}
}
}

View File

@ -225,7 +225,7 @@ namespace Jellyfin.Server.SocketSharp
if (starts_with) if (starts_with)
{ {
return StrUtils.StartsWith(ContentType, ct, true); return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase);
} }
return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
@ -324,215 +324,6 @@ namespace Jellyfin.Server.SocketSharp
return result.ToString(); 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 private class HttpMultipart
{ {
@ -606,12 +397,12 @@ namespace Jellyfin.Server.SocketSharp
string header; string header;
while ((header = ReadHeaders()) != null) while ((header = ReadHeaders()) != null)
{ {
if (StrUtils.StartsWith(header, "Content-Disposition:", true)) if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase))
{ {
elem.Name = GetContentDispositionAttribute(header, "name"); elem.Name = GetContentDispositionAttribute(header, "name");
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); 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.ContentType = header.Substring("Content-Type:".Length).Trim();
elem.Encoding = GetEncoding(elem.ContentType); elem.Encoding = GetEncoding(elem.ContentType);
@ -730,13 +521,14 @@ namespace Jellyfin.Server.SocketSharp
return false; return false;
} }
if (!StrUtils.EndsWith(line, boundary, false)) if (!line.EndsWith(boundary, StringComparison.Ordinal))
{ {
return true; return true;
} }
} }
catch catch
{ {
} }
return false; return false;

View File

@ -44,10 +44,11 @@ namespace Jellyfin.Server.SocketSharp
socket.OnMessage += OnSocketMessage; socket.OnMessage += OnSocketMessage;
socket.OnClose += OnSocketClose; socket.OnClose += OnSocketClose;
socket.OnError += OnSocketError; socket.OnError += OnSocketError;
WebSocket.ConnectAsServer();
} }
public Task ConnectAsServerAsync()
=> WebSocket.ConnectAsServer();
public Task StartReceive() public Task StartReceive()
{ {
return _taskCompletionSource.Task; return _taskCompletionSource.Task;
@ -133,7 +134,7 @@ namespace Jellyfin.Server.SocketSharp
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
WebSocket.Close(); WebSocket.CloseAsync().GetAwaiter().GetResult();
} }
_disposed = true; _disposed = true;

View File

@ -69,7 +69,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
if (_listener == null) 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; _listener.EnableDualMode = _enableDualMode;
@ -79,22 +79,14 @@ namespace Jellyfin.Server.SocketSharp
_listener.LoadCert(_certificate); _listener.LoadCert(_certificate);
} }
foreach (var prefix in urlPrefixes) _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes);
{ _listener.Prefixes.AddRange(urlPrefixes);
_logger.LogInformation("Adding HttpListener prefix " + prefix);
_listener.Prefixes.Add(prefix);
}
_listener.OnContext = ProcessContext; _listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false);
_listener.Start(); _listener.Start();
} }
private void ProcessContext(HttpListenerContext context)
{
_ = Task.Run(async () => await InitTask(context, _disposeCancellationToken).ConfigureAwait(false));
}
private static void LogRequest(ILogger logger, HttpListenerRequest request) private static void LogRequest(ILogger logger, HttpListenerRequest request)
{ {
var url = request.Url.ToString(); var url = request.Url.ToString();
@ -151,10 +143,7 @@ namespace Jellyfin.Server.SocketSharp
Endpoint = endpoint Endpoint = endpoint
}; };
if (WebSocketConnecting != null) WebSocketConnecting?.Invoke(connectingArgs);
{
WebSocketConnecting(connectingArgs);
}
if (connectingArgs.AllowConnection) if (connectingArgs.AllowConnection)
{ {
@ -165,6 +154,7 @@ namespace Jellyfin.Server.SocketSharp
if (WebSocketConnected != null) if (WebSocketConnected != null)
{ {
var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger); var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger);
await socket.ConnectAsServerAsync().ConfigureAwait(false);
WebSocketConnected(new WebSocketConnectEventArgs WebSocketConnected(new WebSocketConnectEventArgs
{ {
@ -174,7 +164,7 @@ namespace Jellyfin.Server.SocketSharp
Endpoint = endpoint Endpoint = endpoint
}); });
await ReceiveWebSocket(ctx, socket).ConfigureAwait(false); await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false);
} }
} }
else 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 try
{ {

View File

@ -28,30 +28,6 @@ namespace Jellyfin.Server.SocketSharp
// HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); // 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 HttpListenerRequest HttpRequest => request;
public object OriginalRequest => request; public object OriginalRequest => request;
@ -102,7 +78,7 @@ namespace Jellyfin.Server.SocketSharp
name = name.Trim(HttpTrimCharacters); name = name.Trim(HttpTrimCharacters);
// First, check for correctly formed multi-line value // 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; int crlf = 0;
for (int i = 0; i < name.Length; ++i) for (int i = 0; i < name.Length; ++i)
{ {
@ -231,8 +207,15 @@ namespace Jellyfin.Server.SocketSharp
{ {
foreach (var acceptsType in acceptContentTypes) foreach (var acceptsType in acceptContentTypes)
{ {
var contentType = HttpResultFactory.GetRealContentType(acceptsType); // TODO: @bond move to Span when Span.Split lands
acceptsAnything = acceptsAnything || contentType == "*/*"; // https://github.com/dotnet/corefx/issues/26528
var contentType = acceptsType?.Split(';')[0].Trim();
acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase);
if (acceptsAnything)
{
break;
}
} }
if (acceptsAnything) if (acceptsAnything)
@ -241,7 +224,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
return defaultContentType; return defaultContentType;
} }
else if (serverDefaultContentType != null) else
{ {
return serverDefaultContentType; return serverDefaultContentType;
} }
@ -284,11 +267,11 @@ namespace Jellyfin.Server.SocketSharp
private static string GetQueryStringContentType(IRequest httpReq) private static string GetQueryStringContentType(IRequest httpReq)
{ {
var format = httpReq.QueryString["format"]; ReadOnlySpan<char> format = httpReq.QueryString["format"];
if (format == null) if (format == null)
{ {
const int formatMaxLength = 4; const int formatMaxLength = 4;
var pi = httpReq.PathInfo; ReadOnlySpan<char> pi = httpReq.PathInfo;
if (pi == null || pi.Length <= formatMaxLength) if (pi == null || pi.Length <= formatMaxLength)
{ {
return null; return null;
@ -296,7 +279,7 @@ namespace Jellyfin.Server.SocketSharp
if (pi[0] == '/') if (pi[0] == '/')
{ {
pi = pi.Substring(1); pi = pi.Slice(1);
} }
format = LeftPart(pi, '/'); format = LeftPart(pi, '/');
@ -330,6 +313,17 @@ namespace Jellyfin.Server.SocketSharp
return pos == -1 ? strVal : strVal.Substring(0, pos); return pos == -1 ? strVal : strVal.Substring(0, pos);
} }
public static ReadOnlySpan<char> LeftPart(ReadOnlySpan<char> strVal, char needle)
{
if (strVal == null)
{
return null;
}
var pos = strVal.IndexOf(needle);
return pos == -1 ? strVal : strVal.Slice(0, pos);
}
public static string HandlerFactoryPath; public static string HandlerFactoryPath;
private string pathInfo; private string pathInfo;
@ -341,7 +335,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
var mode = HandlerFactoryPath; var mode = HandlerFactoryPath;
var pos = request.RawUrl.IndexOf("?", StringComparison.Ordinal); var pos = request.RawUrl.IndexOf('?', StringComparison.Ordinal);
if (pos != -1) if (pos != -1)
{ {
var path = request.RawUrl.Substring(0, pos); var path = request.RawUrl.Substring(0, pos);
@ -525,10 +519,13 @@ namespace Jellyfin.Server.SocketSharp
public static string NormalizePathInfo(string pathInfo, string handlerPath) public static string NormalizePathInfo(string pathInfo, string handlerPath)
{ {
var trimmed = pathInfo.TrimStart('/'); if (handlerPath != null)
if (handlerPath != null && trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
{ {
return trimmed.Substring(handlerPath.Length); var trimmed = pathInfo.TrimStart('/');
if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase))
{
return trimmed.Substring(handlerPath.Length);
}
} }
return pathInfo; return pathInfo;

View File

@ -55,6 +55,41 @@ namespace Jellyfin.Server.SocketSharp
public QueryParamCollection Headers => _response.Headers; 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) public void AddHeader(string name, string value)
{ {
if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase))
@ -126,41 +161,6 @@ namespace Jellyfin.Server.SocketSharp
_response.Headers.Add("Set-Cookie", cookieStr); _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 public bool SendChunked
{ {
get => _response.SendChunked; get => _response.SendChunked;

View File

@ -9,6 +9,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api namespace MediaBrowser.Api
@ -118,8 +119,7 @@ namespace MediaBrowser.Api
{ {
var options = new DtoOptions(); var options = new DtoOptions();
var hasFields = request as IHasItemFields; if (request is IHasItemFields hasFields)
if (hasFields != null)
{ {
options.Fields = hasFields.GetItemFields(); options.Fields = hasFields.GetItemFields();
} }
@ -133,9 +133,11 @@ namespace MediaBrowser.Api
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1) client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
{ {
var list = options.Fields.ToList(); int oldLen = options.Fields.Length;
list.Add(Model.Querying.ItemFields.RecursiveItemCount); var arr = new ItemFields[oldLen + 1];
options.Fields = list.ToArray(); options.Fields.CopyTo(arr, 0);
arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
options.Fields = arr;
} }
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@ -146,9 +148,12 @@ namespace MediaBrowser.Api
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 || client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1) client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
{ {
var list = options.Fields.ToList();
list.Add(Model.Querying.ItemFields.ChildCount); int oldLen = options.Fields.Length;
options.Fields = list.ToArray(); 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)) if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
{ {
options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray(); if (string.IsNullOrEmpty(hasDtoOptions.EnableImageTypes))
{
options.ImageTypes = Array.Empty<ImageType>();
}
else
{
options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true))
.ToArray();
}
} }
} }

View File

@ -197,16 +197,6 @@ namespace MediaBrowser.Api.ScheduledTasks
throw new ResourceNotFoundException("Task not found"); 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()); 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 // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
var id = GetPathValue(1); 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) if (task == null)
{ {
throw new ResourceNotFoundException("Task not found"); throw new ResourceNotFoundException("Task not found");
} }
var triggerInfos = request; task.Triggers = request.ToArray();
task.Triggers = triggerInfos.ToArray();
} }
} }
} }

View File

@ -112,7 +112,7 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{ {
if (request is GetAlbumArtists) if (request is GetAlbumArtists)
{ {

View File

@ -209,9 +209,9 @@ namespace MediaBrowser.Api.UserLibrary
}; };
} }
protected virtual QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) protected virtual QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{ {
return new QueryResult<Tuple<BaseItem, ItemCounts>>(); return new QueryResult<(BaseItem, ItemCounts)>();
} }
private void SetItemCounts(BaseItemDto dto, ItemCounts counts) private void SetItemCounts(BaseItemDto dto, ItemCounts counts)

View File

@ -396,14 +396,12 @@ namespace MediaBrowser.Api.UserLibrary
public VideoType[] GetVideoTypes() public VideoType[] GetVideoTypes()
{ {
var val = VideoTypes; if (string.IsNullOrEmpty(VideoTypes))
if (string.IsNullOrEmpty(val))
{ {
return new VideoType[] { }; return Array.Empty<VideoType>();
} }
return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray(); return VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
} }
/// <summary> /// <summary>

View File

@ -92,7 +92,7 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{ {
var viewType = GetParentItemViewType(request); var viewType = GetParentItemViewType(request);

View File

@ -12,6 +12,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.UserLibrary namespace MediaBrowser.Api.UserLibrary
{ {
@ -90,7 +91,7 @@ namespace MediaBrowser.Api.UserLibrary
var options = GetDtoOptions(_authContext, request); var options = GetDtoOptions(_authContext, request);
var ancestorIds = new List<Guid>(); var ancestorIds = Array.Empty<Guid>();
var excludeFolderIds = user.Configuration.LatestItemsExcludes; var excludeFolderIds = user.Configuration.LatestItemsExcludes;
if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0)
@ -99,12 +100,12 @@ namespace MediaBrowser.Api.UserLibrary
.Where(i => i is Folder) .Where(i => i is Folder)
.Where(i => !excludeFolderIds.Contains(i.Id.ToString("N"))) .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N")))
.Select(i => i.Id) .Select(i => i.Id)
.ToList(); .ToArray();
} }
var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{ {
OrderBy = new[] { ItemSortBy.DatePlayed }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
IsResumable = true, IsResumable = true,
StartIndex = request.StartIndex, StartIndex = request.StartIndex,
Limit = request.Limit, Limit = request.Limit,
@ -115,7 +116,7 @@ namespace MediaBrowser.Api.UserLibrary
IsVirtualItem = false, IsVirtualItem = false,
CollapseBoxSetItems = false, CollapseBoxSetItems = false,
EnableTotalRecordCount = request.EnableTotalRecordCount, EnableTotalRecordCount = request.EnableTotalRecordCount,
AncestorIds = ancestorIds.ToArray(), AncestorIds = ancestorIds,
IncludeItemTypes = request.GetIncludeItemTypes(), IncludeItemTypes = request.GetIncludeItemTypes(),
ExcludeItemTypes = request.GetExcludeItemTypes(), ExcludeItemTypes = request.GetExcludeItemTypes(),
SearchTerm = request.SearchTerm SearchTerm = request.SearchTerm
@ -155,7 +156,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
private QueryResult<BaseItemDto> GetItems(GetItems request) private QueryResult<BaseItemDto> GetItems(GetItems request)
{ {
var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var user = request.UserId == Guid.Empty ? null : _userManager.GetUserById(request.UserId);
var dtoOptions = GetDtoOptions(_authContext, request); var dtoOptions = GetDtoOptions(_authContext, request);
@ -190,11 +191,8 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary> /// </summary>
private QueryResult<BaseItem> GetQueryResult(GetItems request, DtoOptions dtoOptions, User user) private QueryResult<BaseItem> GetQueryResult(GetItems request, DtoOptions dtoOptions, User user)
{ {
if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)) if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)
{ || string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
request.ParentId = null;
}
else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
{ {
request.ParentId = null; request.ParentId = null;
} }
@ -227,6 +225,16 @@ namespace MediaBrowser.Api.UserLibrary
request.IncludeItemTypes = "Playlist"; 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<BaseItem>
{
Items = Array.Empty<BaseItem>(),
TotalRecordCount = 0
};
}
if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null) if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null)
{ {
return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); return folder.GetItems(GetItemsQuery(request, dtoOptions, user));

View File

@ -83,7 +83,7 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{ {
return LibraryManager.GetMusicGenres(query); return LibraryManager.GetMusicGenres(query);
} }

View File

@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
throw new NotImplementedException(); throw new NotImplementedException();
} }
protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{ {
var items = LibraryManager.GetPeopleItems(new InternalPeopleQuery var items = LibraryManager.GetPeopleItems(new InternalPeopleQuery
{ {
@ -109,10 +109,10 @@ namespace MediaBrowser.Api.UserLibrary
NameContains = query.NameContains ?? query.SearchTerm NameContains = query.NameContains ?? query.SearchTerm
}); });
return new QueryResult<Tuple<BaseItem, ItemCounts>> return new QueryResult<(BaseItem, ItemCounts)>
{ {
TotalRecordCount = items.Count, TotalRecordCount = items.Count,
Items = items.Take(query.Limit ?? int.MaxValue).Select(i => new Tuple<BaseItem, ItemCounts>(i, new ItemCounts())).ToArray() Items = items.Take(query.Limit ?? int.MaxValue).Select(i => (i as BaseItem, new ItemCounts())).ToArray()
}; };
} }

View File

@ -91,7 +91,7 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query) protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{ {
return LibraryManager.GetStudios(query); return LibraryManager.GetStudios(query);
} }

View File

@ -41,12 +41,6 @@ namespace MediaBrowser.Common.Configuration
/// <value>The plugin configurations path.</value> /// <value>The plugin configurations path.</value>
string PluginConfigurationsPath { get; } string PluginConfigurationsPath { get; }
/// <summary>
/// Gets the path to where temporary update files will be stored
/// </summary>
/// <value>The plugin configurations path.</value>
string TempUpdatePath { get; }
/// <summary> /// <summary>
/// Gets the path to the log directory /// Gets the path to the log directory
/// </summary> /// </summary>

View File

@ -72,12 +72,6 @@ namespace MediaBrowser.Common
/// <value>The application user agent.</value> /// <value>The application user agent.</value>
string ApplicationUserAgent { get; } string ApplicationUserAgent { get; }
/// <summary>
/// Gets or sets a value indicating whether this instance can self update.
/// </summary>
/// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
bool CanSelfUpdate { get; }
/// <summary> /// <summary>
/// Gets the exports. /// Gets the exports.
/// </summary> /// </summary>
@ -86,12 +80,6 @@ namespace MediaBrowser.Common
/// <returns>IEnumerable{``0}.</returns> /// <returns>IEnumerable{``0}.</returns>
IEnumerable<T> GetExports<T>(bool manageLifetime = true); IEnumerable<T> GetExports<T>(bool manageLifetime = true);
/// <summary>
/// Updates the application.
/// </summary>
/// <returns>Task.</returns>
Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress);
/// <summary> /// <summary>
/// Resolves this instance. /// Resolves this instance.
/// </summary> /// </summary>

View File

@ -193,7 +193,7 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Updates the item. /// Updates the item.
/// </summary> /// </summary>
void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
/// <summary> /// <summary>
@ -520,12 +520,12 @@ namespace MediaBrowser.Controller.Library
void UpdateMediaPath(string virtualFolderName, MediaPathInfo path); void UpdateMediaPath(string virtualFolderName, MediaPathInfo path);
void RemoveMediaPath(string virtualFolderName, string path); void RemoveMediaPath(string virtualFolderName, string path);
QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query);
int GetCount(InternalItemsQuery query); int GetCount(InternalItemsQuery query);

View File

@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Persistence
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
void SaveItems(List<BaseItem> items, CancellationToken cancellationToken); void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken);
void SaveImages(BaseItem item); void SaveImages(BaseItem item);
@ -141,12 +141,12 @@ namespace MediaBrowser.Controller.Persistence
int GetCount(InternalItemsQuery query); int GetCount(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query); QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query);
List<string> GetMusicGenreNames(); List<string> GetMusicGenreNames();
List<string> GetStudioNames(); List<string> GetStudioNames();

View File

@ -7,20 +7,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary> /// </summary>
public class BaseApplicationConfiguration public class BaseApplicationConfiguration
{ {
// TODO: @bond Remove?
/// <summary>
/// Gets or sets a value indicating whether [enable debug level logging].
/// </summary>
/// <value><c>true</c> if [enable debug level logging]; otherwise, <c>false</c>.</value>
public bool EnableDebugLevelLogging { get; set; }
/// <summary>
/// Enable automatically and silently updating of the application
/// </summary>
/// <value><c>true</c> if [enable auto update]; otherwise, <c>false</c>.</value>
public bool EnableAutoUpdate { get; set; }
// TODO: @bond Remove?
/// <summary> /// <summary>
/// The number of days we should retain log files /// The number of days we should retain log files
/// </summary> /// </summary>
@ -44,7 +30,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary> /// </summary>
public BaseApplicationConfiguration() public BaseApplicationConfiguration()
{ {
EnableAutoUpdate = true;
LogFileRetentionDays = 3; LogFileRetentionDays = 3;
} }
} }

View File

@ -14,6 +14,14 @@ namespace MediaBrowser.Model.Serialization
/// <exception cref="ArgumentNullException">obj</exception> /// <exception cref="ArgumentNullException">obj</exception>
void SerializeToStream(object obj, Stream stream); void SerializeToStream(object obj, Stream stream);
/// <summary>
/// Serializes to stream.
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="stream">The stream.</param>
/// <exception cref="ArgumentNullException">obj</exception>
void SerializeToStream<T>(T obj, Stream stream);
/// <summary> /// <summary>
/// Serializes to file. /// Serializes to file.
/// </summary> /// </summary>

View File

@ -60,12 +60,6 @@ namespace MediaBrowser.Model.System
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
public bool CanSelfRestart { get; set; } public bool CanSelfRestart { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance can self update.
/// </summary>
/// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
public bool CanSelfUpdate { get; set; }
public bool CanLaunchWebBrowser { get; set; } public bool CanLaunchWebBrowser { get; set; }
/// <summary> /// <summary>

View File

@ -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
{
/// <summary>
/// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
/// A copy of StablePriorityQueue which also has generic priority-type
/// </summary>
/// <typeparam name="TItem">The values in the queue. Must extend the GenericPriorityQueue class</typeparam>
/// <typeparam name="TPriority">The priority-type. Must extend IComparable&lt;TPriority&gt;</typeparam>
public sealed class GenericPriorityQueue<TItem, TPriority> : IFixedSizePriorityQueue<TItem, TPriority>
where TItem : GenericPriorityQueueNode<TPriority>
where TPriority : IComparable<TPriority>
{
private int _numNodes;
private TItem[] _nodes;
private long _numNodesEverEnqueued;
/// <summary>
/// Instantiate a new Priority Queue
/// </summary>
/// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause undefined behavior)</param>
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;
}
/// <summary>
/// Returns the number of nodes in the queue.
/// O(1)
/// </summary>
public int Count => _numNodes;
/// <summary>
/// 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)
/// </summary>
public int MaxSize => _nodes.Length - 1;
/// <summary>
/// Removes every node from the queue.
/// O(n) (So, don't do this often!)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
Array.Clear(_nodes, 1, _numNodes);
_numNodes = 0;
}
/// <summary>
/// Returns (in O(1)!) whether the given node is in the queue. O(1)
/// </summary>
[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);
}
/// <summary>
/// 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)
/// </summary>
[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;
}
}
}
/// <summary>
/// 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
/// </summary>
[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));
}
/// <summary>
/// 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)
/// </summary>
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;
}
/// <summary>
/// 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)
/// </summary>
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;
}
/// <summary>
/// Returns the head of the queue, without removing it (use Dequeue() for that).
/// If the queue is empty, behavior is undefined.
/// O(1)
/// </summary>
public TItem First
{
get
{
#if DEBUG
if (_numNodes <= 0)
{
throw new InvalidOperationException("Cannot call .First on an empty queue");
}
#endif
return _nodes[1];
}
}
/// <summary>
/// This method must be called on a node every time its priority changes while it is in the queue.
/// <b>Forgetting to call this method will result in a corrupted queue!</b>
/// Calling this method on a node not in the queue results in undefined behavior
/// O(log n)
/// </summary>
[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);
}
}
/// <summary>
/// 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)
/// </summary>
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<TItem> GetEnumerator()
{
for (int i = 1; i <= _numNodes; i++)
yield return _nodes[i];
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// <b>Should not be called in production code.</b>
/// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue.
/// </summary>
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;
}
}
}

View File

@ -1,22 +0,0 @@
namespace Priority_Queue
{
/// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
public class GenericPriorityQueueNode<TPriority>
{
/// <summary>
/// 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
/// </summary>
public TPriority Priority { get; protected internal set; }
/// <summary>
/// Represents the current position in the queue
/// </summary>
public int QueueIndex { get; internal set; }
/// <summary>
/// Represents the order the node was inserted in
/// </summary>
public long InsertionIndex { get; internal set; }
}
}

View File

@ -1,24 +0,0 @@
using System;
namespace Priority_Queue
{
/// <summary>
/// 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)
/// </summary>
internal interface IFixedSizePriorityQueue<TItem, in TPriority> : IPriorityQueue<TItem, TPriority>
where TPriority : IComparable<TPriority>
{
/// <summary>
/// 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
/// </summary>
void Resize(int maxNodes);
/// <summary>
/// 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.
/// </summary>
int MaxSize { get; }
}
}

View File

@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
namespace Priority_Queue
{
/// <summary>
/// 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.
/// </summary>
public interface IPriorityQueue<TItem, in TPriority> : IEnumerable<TItem>
where TPriority : IComparable<TPriority>
{
/// <summary>
/// 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.
/// </summary>
void Enqueue(TItem node, TPriority priority);
/// <summary>
/// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
/// </summary>
bool TryDequeue(out TItem item);
/// <summary>
/// Removes every node from the queue.
/// </summary>
void Clear();
/// <summary>
/// Returns whether the given node is in the queue.
/// </summary>
bool Contains(TItem node);
/// <summary>
/// Removes a node from the queue. The node does not need to be the head of the queue.
/// </summary>
void Remove(TItem node);
/// <summary>
/// Call this method to change the priority of a node.
/// </summary>
void UpdatePriority(TItem node, TPriority priority);
/// <summary>
/// Returns the head of the queue, without removing it (use Dequeue() for that).
/// </summary>
TItem First { get; }
/// <summary>
/// Returns the number of nodes in the queue.
/// </summary>
int Count { get; }
}
}

View File

@ -1,247 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Priority_Queue
{
/// <summary>
/// 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
/// </summary>
/// <typeparam name="TItem">The type to enqueue</typeparam>
/// <typeparam name="TPriority">The priority-type to use for nodes. Must extend IComparable&lt;TPriority&gt;</typeparam>
public class SimplePriorityQueue<TItem, TPriority> : IPriorityQueue<TItem, TPriority>
where TPriority : IComparable<TPriority>
{
private class SimpleNode : GenericPriorityQueueNode<TPriority>
{
public TItem Data { get; private set; }
public SimpleNode(TItem data)
{
Data = data;
}
}
private const int INITIAL_QUEUE_SIZE = 10;
private readonly GenericPriorityQueue<SimpleNode, TPriority> _queue;
public SimplePriorityQueue()
{
_queue = new GenericPriorityQueue<SimpleNode, TPriority>(INITIAL_QUEUE_SIZE);
}
/// <summary>
/// Given an item of type T, returns the exist SimpleNode in the queue
/// </summary>
private SimpleNode GetExistingNode(TItem item)
{
var comparer = EqualityComparer<TItem>.Default;
foreach (var node in _queue)
{
if (comparer.Equals(node.Data, item))
{
return node;
}
}
throw new InvalidOperationException("Item cannot be found in queue: " + item);
}
/// <summary>
/// Returns the number of nodes in the queue.
/// O(1)
/// </summary>
public int Count
{
get
{
lock (_queue)
{
return _queue.Count;
}
}
}
/// <summary>
/// Returns the head of the queue, without removing it (use Dequeue() for that).
/// Throws an exception when the queue is empty.
/// O(1)
/// </summary>
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));
}
}
}
/// <summary>
/// Removes every node from the queue.
/// O(n)
/// </summary>
public void Clear()
{
lock (_queue)
{
_queue.Clear();
}
}
/// <summary>
/// Returns whether the given item is in the queue.
/// O(n)
/// </summary>
public bool Contains(TItem item)
{
lock (_queue)
{
var comparer = EqualityComparer<TItem>.Default;
foreach (var node in _queue)
{
if (comparer.Equals(node.Data, item))
{
return true;
}
}
return false;
}
}
/// <summary>
/// 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)
/// </summary>
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;
}
}
/// <summary>
/// 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)
/// </summary>
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);
}
}
/// <summary>
/// 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)
/// </summary>
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);
}
}
}
/// <summary>
/// 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 <i>and</i> be able
/// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
/// O(n)
/// </summary>
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<TItem> GetEnumerator()
{
var queueData = new List<TItem>();
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();
}
}
}
/// <summary>
/// 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
/// </summary>
/// <typeparam name="TItem">The type to enqueue</typeparam>
public class SimplePriorityQueue<TItem> : SimplePriorityQueue<TItem, float> { }
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@ -11,6 +11,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
<PackageReference Include="PlaylistsNET" Version="1.0.2" /> <PackageReference Include="PlaylistsNET" Version="1.0.2" />
</ItemGroup> </ItemGroup>

View File

@ -74,18 +74,20 @@ namespace SocketHttpListener
} }
} }
private static byte[] readBytes(this Stream stream, byte[] buffer, int offset, int length) private static async Task<byte[]> 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) if (len < 1)
return buffer.SubArray(0, offset); return buffer.SubArray(0, offset);
var tmp = 0; var tmp = 0;
while (len < length) 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) if (tmp < 1)
{
break; break;
}
len += tmp; len += tmp;
} }
@ -95,10 +97,9 @@ namespace SocketHttpListener
: buffer; : buffer;
} }
private static bool readBytes( private static async Task<bool> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest)
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; var len = bytes.Length;
dest.Write(bytes, 0, len); dest.Write(bytes, 0, len);
@ -109,16 +110,16 @@ namespace SocketHttpListener
#region Internal Methods #region Internal Methods
internal static byte[] Append(this ushort code, string reason) internal static async Task<byte[]> AppendAsync(this ushort code, string reason)
{ {
using (var buffer = new MemoryStream()) using (var buffer = new MemoryStream())
{ {
var tmp = code.ToByteArrayInternally(ByteOrder.Big); 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) if (reason != null && reason.Length > 0)
{ {
tmp = Encoding.UTF8.GetBytes(reason); tmp = Encoding.UTF8.GetBytes(reason);
buffer.Write(tmp, 0, tmp.Length); await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false);
} }
return buffer.ToArray(); return buffer.ToArray();
@ -331,12 +332,10 @@ namespace SocketHttpListener
: string.Format("\"{0}\"", value.Replace("\"", "\\\"")); : string.Format("\"{0}\"", value.Replace("\"", "\\\""));
} }
internal static byte[] ReadBytes(this Stream stream, int length) internal static Task<byte[]> ReadBytesAsync(this Stream stream, int length)
{ => stream.ReadBytesAsync(new byte[length], 0, length);
return stream.readBytes(new byte[length], 0, length);
}
internal static byte[] ReadBytes(this Stream stream, long length, int bufferLength) internal static async Task<byte[]> ReadBytesAsync(this Stream stream, long length, int bufferLength)
{ {
using (var result = new MemoryStream()) using (var result = new MemoryStream())
{ {
@ -347,7 +346,7 @@ namespace SocketHttpListener
var end = false; var end = false;
for (long i = 0; i < count; i++) 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; end = true;
break; break;
@ -355,26 +354,14 @@ namespace SocketHttpListener
} }
if (!end && rem > 0) 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(); return result.ToArray();
} }
} }
internal static async Task<byte[]> 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) internal static string RemovePrefix(this string value, params string[] prefixes)
{ {
var i = 0; var i = 0;
@ -493,19 +480,16 @@ namespace SocketHttpListener
return string.Format("{0}; {1}", m, parameters.ToString("; ")); return string.Format("{0}; {1}", m, parameters.ToString("; "));
} }
internal static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
return new List<TSource>(source);
}
internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder) 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) 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) internal static string TrimEndSlash(this string value)
@ -852,14 +836,17 @@ namespace SocketHttpListener
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// <paramref name="src"/> is <see langword="null"/>. /// <paramref name="src"/> is <see langword="null"/>.
/// </exception> /// </exception>
public static byte[] ToHostOrder(this byte[] src, ByteOrder srcOrder) public static void ToHostOrder(this byte[] src, ByteOrder srcOrder)
{ {
if (src == null) if (src == null)
{
throw new ArgumentNullException(nameof(src)); throw new ArgumentNullException(nameof(src));
}
return src.Length > 1 && !srcOrder.IsHostOrder() if (src.Length > 1 && !srcOrder.IsHostOrder())
? src.Reverse() {
: src; Array.Reverse(src);
}
} }
/// <summary> /// <summary>

View File

@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
@ -18,47 +17,55 @@ namespace SocketHttpListener.Net
internal ISocketFactory SocketFactory { get; private set; } internal ISocketFactory SocketFactory { get; private set; }
internal IFileSystem FileSystem { get; private set; } internal IFileSystem FileSystem { get; private set; }
internal IStreamHelper StreamHelper { get; private set; } internal IStreamHelper StreamHelper { get; private set; }
internal INetworkManager NetworkManager { get; private set; }
internal IEnvironmentInfo EnvironmentInfo { get; private set; } internal IEnvironmentInfo EnvironmentInfo { get; private set; }
public bool EnableDualMode { get; set; } public bool EnableDualMode { get; set; }
AuthenticationSchemes auth_schemes; private AuthenticationSchemes auth_schemes;
HttpListenerPrefixCollection prefixes; private HttpListenerPrefixCollection prefixes;
AuthenticationSchemeSelector auth_selector; private AuthenticationSchemeSelector auth_selector;
string realm; private string realm;
bool unsafe_ntlm_auth; private bool unsafe_ntlm_auth;
bool listening; private bool listening;
bool disposed; private bool disposed;
Dictionary<HttpListenerContext, HttpListenerContext> registry; // Dictionary<HttpListenerContext,HttpListenerContext> private Dictionary<HttpListenerContext, HttpListenerContext> registry;
Dictionary<HttpConnection, HttpConnection> connections; private Dictionary<HttpConnection, HttpConnection> connections;
private ILogger _logger; private ILogger _logger;
private X509Certificate _certificate; private X509Certificate _certificate;
public Action<HttpListenerContext> OnContext { get; set; } public Action<HttpListenerContext> OnContext { get; set; }
public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, public HttpListener(
INetworkManager networkManager, IStreamHelper streamHelper, IFileSystem fileSystem, ILogger logger,
ICryptoProvider cryptoProvider,
ISocketFactory socketFactory,
IStreamHelper streamHelper,
IFileSystem fileSystem,
IEnvironmentInfo environmentInfo) IEnvironmentInfo environmentInfo)
{ {
_logger = logger; _logger = logger;
CryptoProvider = cryptoProvider; CryptoProvider = cryptoProvider;
SocketFactory = socketFactory; SocketFactory = socketFactory;
NetworkManager = networkManager;
StreamHelper = streamHelper; StreamHelper = streamHelper;
FileSystem = fileSystem; FileSystem = fileSystem;
EnvironmentInfo = environmentInfo; EnvironmentInfo = environmentInfo;
prefixes = new HttpListenerPrefixCollection(logger, this); prefixes = new HttpListenerPrefixCollection(logger, this);
registry = new Dictionary<HttpListenerContext, HttpListenerContext>(); registry = new Dictionary<HttpListenerContext, HttpListenerContext>();
connections = new Dictionary<HttpConnection, HttpConnection>(); connections = new Dictionary<HttpConnection, HttpConnection>();
auth_schemes = AuthenticationSchemes.Anonymous; auth_schemes = AuthenticationSchemes.Anonymous;
} }
public HttpListener(ILogger logger, X509Certificate certificate, ICryptoProvider cryptoProvider, public HttpListener(
ISocketFactory socketFactory, INetworkManager networkManager, IStreamHelper streamHelper, ILogger logger,
IFileSystem fileSystem, IEnvironmentInfo environmentInfo) X509Certificate certificate,
: this(logger, cryptoProvider, socketFactory, networkManager, streamHelper, fileSystem, environmentInfo) ICryptoProvider cryptoProvider,
ISocketFactory socketFactory,
IStreamHelper streamHelper,
IFileSystem fileSystem,
IEnvironmentInfo environmentInfo)
: this(logger, cryptoProvider, socketFactory, streamHelper, fileSystem, environmentInfo)
{ {
_certificate = certificate; _certificate = certificate;
} }

View File

@ -7,18 +7,18 @@ namespace SocketHttpListener.Net
{ {
public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable
{ {
List<string> prefixes = new List<string>(); private List<string> _prefixes = new List<string>();
HttpListener listener; private HttpListener _listener;
private ILogger _logger; private ILogger _logger;
internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener) internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener)
{ {
_logger = logger; _logger = logger;
this.listener = listener; _listener = listener;
} }
public int Count => prefixes.Count; public int Count => _prefixes.Count;
public bool IsReadOnly => false; public bool IsReadOnly => false;
@ -26,61 +26,90 @@ namespace SocketHttpListener.Net
public void Add(string uriPrefix) public void Add(string uriPrefix)
{ {
listener.CheckDisposed(); _listener.CheckDisposed();
//ListenerPrefix.CheckUri(uriPrefix); //ListenerPrefix.CheckUri(uriPrefix);
if (prefixes.Contains(uriPrefix)) if (_prefixes.Contains(uriPrefix))
{
return; return;
}
prefixes.Add(uriPrefix); _prefixes.Add(uriPrefix);
if (listener.IsListening) if (_listener.IsListening)
HttpEndPointManager.AddPrefix(_logger, uriPrefix, listener); {
HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener);
}
}
public void AddRange(IEnumerable<string> 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() public void Clear()
{ {
listener.CheckDisposed(); _listener.CheckDisposed();
prefixes.Clear(); _prefixes.Clear();
if (listener.IsListening) if (_listener.IsListening)
HttpEndPointManager.RemoveListener(_logger, listener); {
HttpEndPointManager.RemoveListener(_logger, _listener);
}
} }
public bool Contains(string uriPrefix) public bool Contains(string uriPrefix)
{ {
listener.CheckDisposed(); _listener.CheckDisposed();
return prefixes.Contains(uriPrefix); return _prefixes.Contains(uriPrefix);
} }
public void CopyTo(string[] array, int offset) public void CopyTo(string[] array, int offset)
{ {
listener.CheckDisposed(); _listener.CheckDisposed();
prefixes.CopyTo(array, offset); _prefixes.CopyTo(array, offset);
} }
public void CopyTo(Array array, int offset) public void CopyTo(Array array, int offset)
{ {
listener.CheckDisposed(); _listener.CheckDisposed();
((ICollection)prefixes).CopyTo(array, offset); ((ICollection)_prefixes).CopyTo(array, offset);
} }
public IEnumerator<string> GetEnumerator() public IEnumerator<string> GetEnumerator()
{ {
return prefixes.GetEnumerator(); return _prefixes.GetEnumerator();
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return prefixes.GetEnumerator(); return _prefixes.GetEnumerator();
} }
public bool Remove(string uriPrefix) public bool Remove(string uriPrefix)
{ {
listener.CheckDisposed(); _listener.CheckDisposed();
if (uriPrefix == null) if (uriPrefix == null)
{
throw new ArgumentNullException(nameof(uriPrefix)); throw new ArgumentNullException(nameof(uriPrefix));
}
bool result = prefixes.Remove(uriPrefix); bool result = _prefixes.Remove(uriPrefix);
if (result && listener.IsListening) if (result && _listener.IsListening)
HttpEndPointManager.RemovePrefix(_logger, uriPrefix, listener); {
HttpEndPointManager.RemovePrefix(_logger, uriPrefix, _listener);
}
return result; return result;
} }

View File

@ -30,9 +30,9 @@ namespace SocketHttpListener
private CookieCollection _cookies; private CookieCollection _cookies;
private AutoResetEvent _exitReceiving; private AutoResetEvent _exitReceiving;
private object _forConn; private object _forConn;
private object _forEvent; private readonly SemaphoreSlim _forEvent = new SemaphoreSlim(1, 1);
private object _forMessageEventQueue; private object _forMessageEventQueue;
private object _forSend; private readonly SemaphoreSlim _forSend = new SemaphoreSlim(1, 1);
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private Queue<MessageEventArgs> _messageEventQueue; private Queue<MessageEventArgs> _messageEventQueue;
private string _protocol; private string _protocol;
@ -109,12 +109,15 @@ namespace SocketHttpListener
#region Private Methods #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) lock (_forConn)
{ {
@ -126,11 +129,12 @@ namespace SocketHttpListener
_readyState = WebSocketState.CloseSent; _readyState = WebSocketState.CloseSent;
} }
var e = new CloseEventArgs(payload); var e = new CloseEventArgs(payload)
e.WasClean = {
closeHandshake( WasClean = await CloseHandshakeAsync(
send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null, send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null,
wait ? 1000 : 0); wait ? 1000 : 0).ConfigureAwait(false)
};
_readyState = WebSocketState.Closed; _readyState = WebSocketState.Closed;
try try
@ -143,9 +147,9 @@ namespace SocketHttpListener
} }
} }
private bool closeHandshake(byte[] frameAsBytes, int millisecondsTimeout) private async Task<bool> CloseHandshakeAsync(byte[] frameAsBytes, int millisecondsTimeout)
{ {
var sent = frameAsBytes != null && writeBytes(frameAsBytes); var sent = frameAsBytes != null && await WriteBytesAsync(frameAsBytes).ConfigureAwait(false);
var received = var received =
millisecondsTimeout == 0 || millisecondsTimeout == 0 ||
(sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout)); (sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout));
@ -189,11 +193,11 @@ namespace SocketHttpListener
_context = null; _context = null;
} }
private bool concatenateFragmentsInto(Stream dest) private async Task<bool> ConcatenateFragmentsIntoAsync(Stream dest)
{ {
while (true) while (true)
{ {
var frame = WebSocketFrame.Read(_stream, true); var frame = await WebSocketFrame.ReadAsync(_stream, true).ConfigureAwait(false);
if (frame.IsFinal) if (frame.IsFinal)
{ {
/* FINAL */ /* FINAL */
@ -221,7 +225,7 @@ namespace SocketHttpListener
// CLOSE // CLOSE
if (frame.IsClose) if (frame.IsClose)
return processCloseFrame(frame); return await ProcessCloseFrameAsync(frame).ConfigureAwait(false);
} }
else else
{ {
@ -236,10 +240,10 @@ namespace SocketHttpListener
} }
// ? // ?
return processUnsupportedFrame( return await ProcessUnsupportedFrameAsync(
frame, frame,
CloseStatusCode.IncorrectData, 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; return true;
@ -299,44 +303,42 @@ namespace SocketHttpListener
_compression = CompressionMethod.None; _compression = CompressionMethod.None;
_cookies = new CookieCollection(); _cookies = new CookieCollection();
_forConn = new object(); _forConn = new object();
_forEvent = new object();
_forSend = new object();
_messageEventQueue = new Queue<MessageEventArgs>(); _messageEventQueue = new Queue<MessageEventArgs>();
_forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot; _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
_readyState = WebSocketState.Connecting; _readyState = WebSocketState.Connecting;
} }
private void open() private async Task OpenAsync()
{ {
try try
{ {
startReceiving(); 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) 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<bool> ProcessCloseFrameAsync(WebSocketFrame frame)
{ {
var payload = frame.PayloadData; var payload = frame.PayloadData;
close(payload, !payload.ContainsReservedCloseStatusCode, false); await CloseAsync(payload, !payload.ContainsReservedCloseStatusCode, false).ConfigureAwait(false);
return false; return false;
} }
@ -352,7 +354,7 @@ namespace SocketHttpListener
return true; return true;
} }
private void processException(Exception exception, string message) private async Task ProcessExceptionAsync(Exception exception, string message)
{ {
var code = CloseStatusCode.Abnormal; var code = CloseStatusCode.Abnormal;
var reason = message; var reason = message;
@ -365,25 +367,31 @@ namespace SocketHttpListener
error(message ?? code.GetMessage(), exception); error(message ?? code.GetMessage(), exception);
if (_readyState == WebSocketState.Connecting) if (_readyState == WebSocketState.Connecting)
Close(HttpStatusCode.BadRequest); {
await CloseAsync(HttpStatusCode.BadRequest).ConfigureAwait(false);
}
else else
close(code, reason ?? code.GetMessage(), false); {
await CloseAsync(code, reason ?? code.GetMessage(), false).ConfigureAwait(false);
}
} }
private bool processFragmentedFrame(WebSocketFrame frame) private Task<bool> ProcessFragmentedFrameAsync(WebSocketFrame frame)
{ {
return frame.IsContinuation // Not first fragment return frame.IsContinuation // Not first fragment
? true ? Task.FromResult(true)
: processFragments(frame); : ProcessFragmentsAsync(frame);
} }
private bool processFragments(WebSocketFrame first) private async Task<bool> ProcessFragmentsAsync(WebSocketFrame first)
{ {
using (var buff = new MemoryStream()) using (var buff = new MemoryStream())
{ {
buff.WriteBytes(first.PayloadData.ApplicationData); buff.WriteBytes(first.PayloadData.ApplicationData);
if (!concatenateFragmentsInto(buff)) if (!await ConcatenateFragmentsIntoAsync(buff).ConfigureAwait(false))
{
return false; return false;
}
byte[] data; byte[] data;
if (_compression != CompressionMethod.None) if (_compression != CompressionMethod.None)
@ -412,36 +420,38 @@ namespace SocketHttpListener
return true; return true;
} }
private bool processUnsupportedFrame(WebSocketFrame frame, CloseStatusCode code, string reason) private async Task<bool> ProcessUnsupportedFrameAsync(WebSocketFrame frame, CloseStatusCode code, string reason)
{ {
processException(new WebSocketException(code, reason), null); await ProcessExceptionAsync(new WebSocketException(code, reason), null).ConfigureAwait(false);
return false; return false;
} }
private bool processWebSocketFrame(WebSocketFrame frame) private Task<bool> ProcessWebSocketFrameAsync(WebSocketFrame frame)
{ {
// TODO: @bond change to if/else chain
return frame.IsCompressed && _compression == CompressionMethod.None return frame.IsCompressed && _compression == CompressionMethod.None
? processUnsupportedFrame( ? ProcessUnsupportedFrameAsync(
frame, frame,
CloseStatusCode.IncorrectData, CloseStatusCode.IncorrectData,
"A compressed data has been received without available decompression method.") "A compressed data has been received without available decompression method.")
: frame.IsFragmented : frame.IsFragmented
? processFragmentedFrame(frame) ? ProcessFragmentedFrameAsync(frame)
: frame.IsData : frame.IsData
? processDataFrame(frame) ? Task.FromResult(processDataFrame(frame))
: frame.IsPing : frame.IsPing
? processPingFrame(frame) ? Task.FromResult(processPingFrame(frame))
: frame.IsPong : frame.IsPong
? processPongFrame(frame) ? Task.FromResult(processPongFrame(frame))
: frame.IsClose : frame.IsClose
? processCloseFrame(frame) ? ProcessCloseFrameAsync(frame)
: processUnsupportedFrame(frame, CloseStatusCode.PolicyViolation, null); : ProcessUnsupportedFrameAsync(frame, CloseStatusCode.PolicyViolation, null);
} }
private bool send(Opcode opcode, Stream stream) private async Task<bool> SendAsync(Opcode opcode, Stream stream)
{ {
lock (_forSend) await _forSend.WaitAsync().ConfigureAwait(false);
try
{ {
var src = stream; var src = stream;
var compressed = false; var compressed = false;
@ -454,7 +464,7 @@ namespace SocketHttpListener
compressed = true; compressed = true;
} }
sent = send(opcode, Mask.Unmask, stream, compressed); sent = await SendAsync(opcode, Mask.Unmask, stream, compressed).ConfigureAwait(false);
if (!sent) if (!sent)
error("Sending a data has been interrupted."); error("Sending a data has been interrupted.");
} }
@ -472,16 +482,20 @@ namespace SocketHttpListener
return sent; return sent;
} }
finally
{
_forSend.Release();
}
} }
private bool send(Opcode opcode, Mask mask, Stream stream, bool compressed) private async Task<bool> SendAsync(Opcode opcode, Mask mask, Stream stream, bool compressed)
{ {
var len = stream.Length; var len = stream.Length;
/* Not fragmented */ /* Not fragmented */
if (len == 0) 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 quo = len / FragmentLength;
var rem = (int)(len % FragmentLength); var rem = (int)(len % FragmentLength);
@ -490,26 +504,26 @@ namespace SocketHttpListener
if (quo == 0) if (quo == 0)
{ {
buff = new byte[rem]; buff = new byte[rem];
return stream.Read(buff, 0, rem) == rem && return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem &&
send(Fin.Final, opcode, mask, buff, compressed); await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false);
} }
buff = new byte[FragmentLength]; buff = new byte[FragmentLength];
if (quo == 1 && rem == 0) if (quo == 1 && rem == 0)
return stream.Read(buff, 0, FragmentLength) == FragmentLength && return await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) == FragmentLength &&
send(Fin.Final, opcode, mask, buff, compressed); await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false);
/* Send fragmented */ /* Send fragmented */
// Begin // Begin
if (stream.Read(buff, 0, FragmentLength) != FragmentLength || if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength ||
!send(Fin.More, opcode, mask, buff, compressed)) !await SendAsync(Fin.More, opcode, mask, buff, compressed).ConfigureAwait(false))
return false; return false;
var n = rem == 0 ? quo - 2 : quo - 1; var n = rem == 0 ? quo - 2 : quo - 1;
for (long i = 0; i < n; i++) for (long i = 0; i < n; i++)
if (stream.Read(buff, 0, FragmentLength) != FragmentLength || if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength ||
!send(Fin.More, Opcode.Cont, mask, buff, compressed)) !await SendAsync(Fin.More, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false))
return false; return false;
// End // End
@ -518,98 +532,88 @@ namespace SocketHttpListener
else else
buff = new byte[rem]; buff = new byte[rem];
return stream.Read(buff, 0, rem) == rem && return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem &&
send(Fin.Final, Opcode.Cont, mask, buff, compressed); 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<bool> SendAsync(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
{ {
lock (_forConn) lock (_forConn)
{ {
if (_readyState != WebSocketState.Open) if (_readyState != WebSocketState.Open)
{ {
return false; return Task.FromResult(false);
} }
return writeBytes( return WriteBytesAsync(
WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray()); WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray());
} }
} }
private Task sendAsync(Opcode opcode, Stream stream)
{
var completionSource = new TaskCompletionSource<bool>();
Task.Run(() =>
{
try
{
send(opcode, stream);
completionSource.TrySetResult(true);
}
catch (Exception ex)
{
completionSource.TrySetException(ex);
}
});
return completionSource.Task;
}
// As server // As server
private bool sendHttpResponse(HttpResponse response) private Task<bool> SendHttpResponseAsync(HttpResponse response)
{ => WriteBytesAsync(response.ToByteArray());
return writeBytes(response.ToByteArray());
}
private void startReceiving() private void startReceiving()
{ {
if (_messageEventQueue.Count > 0) if (_messageEventQueue.Count > 0)
{
_messageEventQueue.Clear(); _messageEventQueue.Clear();
}
_exitReceiving = new AutoResetEvent(false); _exitReceiving = new AutoResetEvent(false);
_receivePong = new AutoResetEvent(false); _receivePong = new AutoResetEvent(false);
Action receive = null; Action receive = null;
receive = () => WebSocketFrame.ReadAsync( receive = async () => await WebSocketFrame.ReadAsync(
_stream, _stream,
true, true,
frame => async frame =>
{ {
if (processWebSocketFrame(frame) && _readyState != WebSocketState.Closed) if (await ProcessWebSocketFrameAsync(frame).ConfigureAwait(false) && _readyState != WebSocketState.Closed)
{ {
receive(); receive();
if (!frame.IsData) if (!frame.IsData)
return; {
return;
}
lock (_forEvent) await _forEvent.WaitAsync().ConfigureAwait(false);
{
try try
{ {
var e = dequeueFromMessageEventQueue(); var e = dequeueFromMessageEventQueue();
if (e != null && _readyState == WebSocketState.Open) if (e != null && _readyState == WebSocketState.Open)
OnMessage.Emit(this, e); {
} OnMessage.Emit(this, e);
catch (Exception ex) }
{ }
processException(ex, "An exception has occurred while OnMessage."); catch (Exception ex)
} {
} await ProcessExceptionAsync(ex, "An exception has occurred while OnMessage.").ConfigureAwait(false);
} }
else if (_exitReceiving != null) finally
{ {
_exitReceiving.Set(); _forEvent.Release();
} }
},
ex => processException(ex, "An exception has occurred while receiving a message.")); }
else if (_exitReceiving != null)
{
_exitReceiving.Set();
}
},
async ex => await ProcessExceptionAsync(ex, "An exception has occurred while receiving a message.")).ConfigureAwait(false);
receive(); receive();
} }
private bool writeBytes(byte[] data) private async Task<bool> WriteBytesAsync(byte[] data)
{ {
try try
{ {
_stream.Write(data, 0, data.Length); await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
return true; return true;
} }
catch (Exception) catch (Exception)
@ -623,10 +627,10 @@ namespace SocketHttpListener
#region Internal Methods #region Internal Methods
// As server // As server
internal void Close(HttpResponse response) internal async Task CloseAsync(HttpResponse response)
{ {
_readyState = WebSocketState.CloseSent; _readyState = WebSocketState.CloseSent;
sendHttpResponse(response); await SendHttpResponseAsync(response).ConfigureAwait(false);
closeServerResources(); closeServerResources();
@ -634,22 +638,20 @@ namespace SocketHttpListener
} }
// As server // As server
internal void Close(HttpStatusCode code) internal Task CloseAsync(HttpStatusCode code)
{ => CloseAsync(createHandshakeCloseResponse(code));
Close(createHandshakeCloseResponse(code));
}
// As server // As server
public void ConnectAsServer() public async Task ConnectAsServer()
{ {
try try
{ {
_readyState = WebSocketState.Open; _readyState = WebSocketState.Open;
open(); await OpenAsync().ConfigureAwait(false);
} }
catch (Exception ex) 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
/// <summary> /// <summary>
/// Closes the WebSocket connection, and releases all associated resources. /// Closes the WebSocket connection, and releases all associated resources.
/// </summary> /// </summary>
public void Close() public Task CloseAsync()
{ {
var msg = _readyState.CheckIfClosable(); var msg = _readyState.CheckIfClosable();
if (msg != null) if (msg != null)
{ {
error(msg); error(msg);
return; return Task.CompletedTask;
} }
var send = _readyState == WebSocketState.Open; var send = _readyState == WebSocketState.Open;
close(new PayloadData(), send, send); return CloseAsync(new PayloadData(), send, send);
} }
/// <summary> /// <summary>
@ -689,11 +691,11 @@ namespace SocketHttpListener
/// <param name="reason"> /// <param name="reason">
/// A <see cref="string"/> that represents the reason for the close. /// A <see cref="string"/> that represents the reason for the close.
/// </param> /// </param>
public void Close(CloseStatusCode code, string reason) public async Task CloseAsync(CloseStatusCode code, string reason)
{ {
byte[] data = null; byte[] data = null;
var msg = _readyState.CheckIfClosable() ?? var msg = _readyState.CheckIfClosable() ??
(data = ((ushort)code).Append(reason)).CheckIfValidControlData("reason"); (data = await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)).CheckIfValidControlData("reason");
if (msg != null) if (msg != null)
{ {
@ -703,7 +705,7 @@ namespace SocketHttpListener
} }
var send = _readyState == WebSocketState.Open && !code.IsReserved(); var send = _readyState == WebSocketState.Open && !code.IsReserved();
close(new PayloadData(data), send, send); await CloseAsync(new PayloadData(data), send, send).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -728,7 +730,7 @@ namespace SocketHttpListener
throw new Exception(msg); throw new Exception(msg);
} }
return sendAsync(Opcode.Binary, new MemoryStream(data)); return SendAsync(Opcode.Binary, new MemoryStream(data));
} }
/// <summary> /// <summary>
@ -753,7 +755,7 @@ namespace SocketHttpListener
throw new Exception(msg); 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 #endregion
@ -768,7 +770,7 @@ namespace SocketHttpListener
/// </remarks> /// </remarks>
void IDisposable.Dispose() void IDisposable.Dispose()
{ {
Close(CloseStatusCode.Away, null); CloseAsync(CloseStatusCode.Away, null).GetAwaiter().GetResult();
} }
#endregion #endregion

View File

@ -2,6 +2,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace SocketHttpListener namespace SocketHttpListener
{ {
@ -177,7 +178,7 @@ namespace SocketHttpListener
return opcode == Opcode.Text || opcode == Opcode.Binary; return opcode == Opcode.Text || opcode == Opcode.Binary;
} }
private static WebSocketFrame read(byte[] header, Stream stream, bool unmask) private static async Task<WebSocketFrame> ReadAsync(byte[] header, Stream stream, bool unmask)
{ {
/* Header */ /* Header */
@ -229,7 +230,7 @@ namespace SocketHttpListener
? 2 ? 2
: 8; : 8;
var extPayloadLen = size > 0 ? stream.ReadBytes(size) : new byte[0]; var extPayloadLen = size > 0 ? await stream.ReadBytesAsync(size).ConfigureAwait(false) : Array.Empty<byte>();
if (size > 0 && extPayloadLen.Length != size) if (size > 0 && extPayloadLen.Length != size)
throw new WebSocketException( throw new WebSocketException(
"The 'Extended Payload Length' of a frame cannot be read from the data source."); "The 'Extended Payload Length' of a frame cannot be read from the data source.");
@ -239,7 +240,7 @@ namespace SocketHttpListener
/* Masking Key */ /* Masking Key */
var masked = mask == Mask.Mask; 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<byte>();
if (masked && maskingKey.Length != 4) if (masked && maskingKey.Length != 4)
throw new WebSocketException( throw new WebSocketException(
"The 'Masking Key' of a frame cannot be read from the data source."); "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."); "The length of 'Payload Data' of a frame is greater than the allowable length.");
data = payloadLen > 126 data = payloadLen > 126
? stream.ReadBytes((long)len, 1024) ? await stream.ReadBytesAsync((long)len, 1024).ConfigureAwait(false)
: stream.ReadBytes((int)len); : await stream.ReadBytesAsync((int)len).ConfigureAwait(false);
//if (data.LongLength != (long)len) //if (data.LongLength != (long)len)
// throw new WebSocketException( // throw new WebSocketException(
@ -273,7 +274,7 @@ namespace SocketHttpListener
} }
else else
{ {
data = new byte[0]; data = Array.Empty<byte>();
} }
var payload = new PayloadData(data, masked); var payload = new PayloadData(data, masked);
@ -281,7 +282,7 @@ namespace SocketHttpListener
{ {
payload.Mask(maskingKey); payload.Mask(maskingKey);
frame._mask = Mask.Unmask; frame._mask = Mask.Unmask;
frame._maskingKey = new byte[0]; frame._maskingKey = Array.Empty<byte>();
} }
frame._payloadData = payload; frame._payloadData = payload;
@ -302,10 +303,10 @@ namespace SocketHttpListener
return new WebSocketFrame(Opcode.Close, mask, payload); return new WebSocketFrame(Opcode.Close, mask, payload);
} }
internal static WebSocketFrame CreateCloseFrame(Mask mask, CloseStatusCode code, string reason) internal static async Task<WebSocketFrame> CreateCloseFrameAsync(Mask mask, CloseStatusCode code, string reason)
{ {
return new WebSocketFrame( 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) internal static WebSocketFrame CreatePingFrame(Mask mask)
@ -329,41 +330,39 @@ namespace SocketHttpListener
return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed); return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed);
} }
internal static WebSocketFrame Read(Stream stream) internal static Task<WebSocketFrame> ReadAsync(Stream stream)
{ => ReadAsync(stream, true);
return Read(stream, true);
}
internal static WebSocketFrame Read(Stream stream, bool unmask) internal static async Task<WebSocketFrame> ReadAsync(Stream stream, bool unmask)
{ {
var header = stream.ReadBytes(2); var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
if (header.Length != 2) if (header.Length != 2)
{
throw new WebSocketException( throw new WebSocketException(
"The header part of a frame cannot be read from the data source."); "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<WebSocketFrame> completed, Action<Exception> error) Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
{ {
try try
{ {
var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
if (header.Length != 2) if (header.Length != 2)
{
throw new WebSocketException( throw new WebSocketException(
"The header part of a frame cannot be read from the data source."); "The header part of a frame cannot be read from the data source.");
}
var frame = read(header, stream, unmask); var frame = await ReadAsync(header, stream, unmask).ConfigureAwait(false);
if (completed != null) completed?.Invoke(frame);
completed(frame);
} }
catch (Exception ex) catch (Exception ex)
{ {
if (error != null) error.Invoke(ex);
{
error(ex);
}
} }
} }

View File

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

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
source ../common.build.sh
VERSION=`get_version ../..`
clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION}

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
source ../common.build.sh
VERSION=`get_version ../..`
package_portable ../.. `pwd`/dist/jellyfin_${VERSION}

View File

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

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
source ../common.build.sh
VERSION=`get_version ../..`
clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION}

View File

@ -1 +0,0 @@
dotnet

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
source ../common.build.sh
VERSION=`get_version ../..`
package_portable ../.. `pwd`/dist/jellyfin_${VERSION}

View File

@ -1 +0,0 @@
dotnet