Merge branch 'beta' of https://github.com/MediaBrowser/Emby into beta
This commit is contained in:
commit
6d40ab7e1c
|
@ -61,7 +61,7 @@ namespace Emby.Common.Implementations.IO
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return Path.DirectorySeparatorChar;
|
return Path.PathSeparator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
#else
|
#else
|
||||||
retVal.ExclusiveAddressUse = false;
|
retVal.ExclusiveAddressUse = false;
|
||||||
#endif
|
#endif
|
||||||
|
//retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
|
||||||
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
|
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
|
||||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP));
|
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP));
|
||||||
|
|
|
@ -63,7 +63,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
}
|
}
|
||||||
}, state);
|
}, state);
|
||||||
#else
|
#else
|
||||||
_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, new AsyncCallback(this.ProcessResponse), state);
|
_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, ProcessResponse, state);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return tcs.Task;
|
return tcs.Task;
|
||||||
|
@ -99,7 +99,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
_Socket.EndSend(result);
|
_Socket.EndSend(result);
|
||||||
taskSource.TrySetResult(true);
|
taskSource.TrySetResult(true);
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
taskSource.TrySetException(ex);
|
taskSource.TrySetException(ex);
|
||||||
}
|
}
|
||||||
|
@ -200,13 +200,6 @@ namespace Emby.Common.Implementations.Net
|
||||||
{
|
{
|
||||||
state.TaskCompletionSource.SetCanceled();
|
state.TaskCompletionSource.SetCanceled();
|
||||||
}
|
}
|
||||||
catch (SocketException se)
|
|
||||||
{
|
|
||||||
if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown)
|
|
||||||
state.TaskCompletionSource.SetException(se);
|
|
||||||
else
|
|
||||||
state.TaskCompletionSource.SetCanceled();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
state.TaskCompletionSource.SetException(ex);
|
state.TaskCompletionSource.SetException(ex);
|
||||||
|
|
|
@ -55,6 +55,8 @@ namespace Emby.Dlna.Main
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
private readonly IEnvironmentInfo _environmentInfo;
|
private readonly IEnvironmentInfo _environmentInfo;
|
||||||
|
|
||||||
|
private ISsdpCommunicationsServer _communicationsServer;
|
||||||
|
|
||||||
public DlnaEntryPoint(IServerConfigurationManager config,
|
public DlnaEntryPoint(IServerConfigurationManager config,
|
||||||
ILogManager logManager,
|
ILogManager logManager,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
|
@ -152,10 +154,18 @@ namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
StartPublishing();
|
if (_communicationsServer == null)
|
||||||
|
{
|
||||||
|
_communicationsServer = new SsdpCommunicationsServer(_socketFactory)
|
||||||
|
{
|
||||||
|
IsShared = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
StartPublishing(_communicationsServer);
|
||||||
_ssdpHandlerStarted = true;
|
_ssdpHandlerStarted = true;
|
||||||
|
|
||||||
StartDeviceDiscovery();
|
StartDeviceDiscovery(_communicationsServer);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -165,20 +175,20 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
private void LogMessage(string msg)
|
private void LogMessage(string msg)
|
||||||
{
|
{
|
||||||
//_logger.Debug(msg);
|
_logger.Debug(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartPublishing()
|
private void StartPublishing(ISsdpCommunicationsServer communicationsServer)
|
||||||
{
|
{
|
||||||
SsdpDevicePublisherBase.LogFunction = LogMessage;
|
SsdpDevicePublisherBase.LogFunction = LogMessage;
|
||||||
_Publisher = new SsdpDevicePublisher(_socketFactory, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
|
_Publisher = new SsdpDevicePublisher(communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartDeviceDiscovery()
|
private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
((DeviceDiscovery)_deviceDiscovery).Start();
|
((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -374,6 +384,12 @@ namespace Emby.Dlna.Main
|
||||||
DisposeDlnaServer();
|
DisposeDlnaServer();
|
||||||
DisposePlayToManager();
|
DisposePlayToManager();
|
||||||
DisposeSsdpHandler();
|
DisposeSsdpHandler();
|
||||||
|
|
||||||
|
if (_communicationsServer != null)
|
||||||
|
{
|
||||||
|
_communicationsServer.Dispose();
|
||||||
|
_communicationsServer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisposeDlnaServer()
|
public void DisposeDlnaServer()
|
||||||
|
|
|
@ -15,6 +15,7 @@ using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Threading;
|
using MediaBrowser.Model.Threading;
|
||||||
using Rssdp;
|
using Rssdp;
|
||||||
|
using Rssdp.Infrastructure;
|
||||||
|
|
||||||
namespace Emby.Dlna.Ssdp
|
namespace Emby.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
|
@ -29,7 +30,7 @@ namespace Emby.Dlna.Ssdp
|
||||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
|
||||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
||||||
|
|
||||||
private SsdpDeviceLocator _DeviceLocator;
|
private SsdpDeviceLocator _deviceLocator;
|
||||||
|
|
||||||
private readonly ITimerFactory _timerFactory;
|
private readonly ITimerFactory _timerFactory;
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
|
@ -45,9 +46,9 @@ namespace Emby.Dlna.Ssdp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this method from somewhere in your code to start the search.
|
// Call this method from somewhere in your code to start the search.
|
||||||
public void BeginSearch()
|
public void Start(ISsdpCommunicationsServer communicationsServer)
|
||||||
{
|
{
|
||||||
_DeviceLocator = new SsdpDeviceLocator(_socketFactory, _timerFactory);
|
_deviceLocator = new SsdpDeviceLocator(communicationsServer, _timerFactory);
|
||||||
|
|
||||||
// (Optional) Set the filter so we only see notifications for devices we care about
|
// (Optional) Set the filter so we only see notifications for devices we care about
|
||||||
// (can be any search target value i.e device type, uuid value etc - any value that appears in the
|
// (can be any search target value i.e device type, uuid value etc - any value that appears in the
|
||||||
|
@ -55,8 +56,8 @@ namespace Emby.Dlna.Ssdp
|
||||||
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
|
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
|
||||||
|
|
||||||
// Connect our event handler so we process devices as they are found
|
// Connect our event handler so we process devices as they are found
|
||||||
_DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
|
_deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
|
||||||
_DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
|
_deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
|
||||||
|
|
||||||
// Perform a search so we don't have to wait for devices to broadcast notifications
|
// Perform a search so we don't have to wait for devices to broadcast notifications
|
||||||
// again to get any results right away (notifications are broadcast periodically).
|
// again to get any results right away (notifications are broadcast periodically).
|
||||||
|
@ -72,9 +73,9 @@ namespace Emby.Dlna.Ssdp
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Enable listening for notifications (optional)
|
// Enable listening for notifications (optional)
|
||||||
_DeviceLocator.StartListeningForNotifications();
|
_deviceLocator.StartListeningForNotifications();
|
||||||
|
|
||||||
await _DeviceLocator.SearchAsync().ConfigureAwait(false);
|
await _deviceLocator.SearchAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -130,11 +131,6 @@ namespace Emby.Dlna.Ssdp
|
||||||
EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
|
EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
BeginSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
|
|
|
@ -31,13 +31,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var httpRequestOptions = new HttpRequestOptions()
|
var httpRequestOptions = new HttpRequestOptions
|
||||||
{
|
{
|
||||||
Url = mediaSource.Path
|
Url = mediaSource.Path,
|
||||||
|
BufferContent = false
|
||||||
};
|
};
|
||||||
|
|
||||||
httpRequestOptions.BufferContent = false;
|
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
|
using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
_logger.Info("Opened recording stream from tuner provider");
|
_logger.Info("Opened recording stream from tuner provider");
|
||||||
|
|
|
@ -291,7 +291,7 @@ namespace MediaBrowser.Server.Mono
|
||||||
{
|
{
|
||||||
public bool IsBsd { get; set; }
|
public bool IsBsd { get; set; }
|
||||||
|
|
||||||
public virtual string GetUserId()
|
public override string GetUserId()
|
||||||
{
|
{
|
||||||
return Syscall.getuid().ToString(CultureInfo.InvariantCulture);
|
return Syscall.getuid().ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,8 +51,7 @@ namespace Rssdp.Infrastructure
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a message to the SSDP multicast address and port.
|
/// Sends a message to the SSDP multicast address and port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageData">A byte array containing the data to send.</param>
|
Task SendMulticastMessage(string message);
|
||||||
Task SendMulticastMessage(byte[] messageData);
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
<Compile Include="SsdpDevicePublisher.cs" />
|
<Compile Include="SsdpDevicePublisher.cs" />
|
||||||
<Compile Include="SsdpDevicePublisherBase.cs" />
|
<Compile Include="SsdpDevicePublisherBase.cs" />
|
||||||
<Compile Include="SsdpEmbeddedDevice.cs" />
|
<Compile Include="SsdpEmbeddedDevice.cs" />
|
||||||
|
<Compile Include="SsdpHelper.cs" />
|
||||||
<Compile Include="SsdpRootDevice.cs" />
|
<Compile Include="SsdpRootDevice.cs" />
|
||||||
<Compile Include="UPnP10DeviceValidator.cs" />
|
<Compile Include="UPnP10DeviceValidator.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -170,12 +170,11 @@ namespace Rssdp.Infrastructure
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a message to the SSDP multicast address and port.
|
/// Sends a message to the SSDP multicast address and port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageData">A byte array containing the data to send.</param>
|
public async Task SendMulticastMessage(string message)
|
||||||
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="messageData"/> argument is null.</exception>
|
|
||||||
/// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception>
|
|
||||||
public async Task SendMulticastMessage(byte[] messageData)
|
|
||||||
{
|
{
|
||||||
if (messageData == null) throw new ArgumentNullException("messageData");
|
if (message == null) throw new ArgumentNullException("messageData");
|
||||||
|
|
||||||
|
byte[] messageData = Encoding.UTF8.GetBytes(message);
|
||||||
|
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
@ -294,21 +293,19 @@ namespace Rssdp.Infrastructure
|
||||||
// Tasks are captured to local variables even if we don't use them just to avoid compiler warnings.
|
// Tasks are captured to local variables even if we don't use them just to avoid compiler warnings.
|
||||||
var t = Task.Run(async () =>
|
var t = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
|
|
||||||
var cancelled = false;
|
var cancelled = false;
|
||||||
while (!cancelled)
|
while (!cancelled)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await socket.ReceiveAsync();
|
var result = await socket.ReceiveAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
if (result.ReceivedBytes > 0)
|
if (result.ReceivedBytes > 0)
|
||||||
{
|
{
|
||||||
// Strange cannot convert compiler error here if I don't explicitly
|
// Strange cannot convert compiler error here if I don't explicitly
|
||||||
// assign or cast to Action first. Assignment is easier to read,
|
// assign or cast to Action first. Assignment is easier to read,
|
||||||
// so went with that.
|
// so went with that.
|
||||||
Action processWork = () => ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint);
|
ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint);
|
||||||
var processTask = Task.Run(processWork);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
|
@ -330,10 +327,12 @@ namespace Rssdp.Infrastructure
|
||||||
lock (_SendSocketSynchroniser)
|
lock (_SendSocketSynchroniser)
|
||||||
{
|
{
|
||||||
if (_SendSocket == null)
|
if (_SendSocket == null)
|
||||||
|
{
|
||||||
_SendSocket = CreateSocketAndListenForResponsesAsync();
|
_SendSocket = CreateSocketAndListenForResponsesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ProcessMessage(string data, IpEndPointInfo endPoint)
|
private void ProcessMessage(string data, IpEndPointInfo endPoint)
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace Rssdp
|
||||||
/// Default constructor. Constructs a new instance using the default <see cref="ISsdpCommunicationsServer"/> and <see cref="ISocketFactory"/> implementations for this platform.
|
/// Default constructor. Constructs a new instance using the default <see cref="ISsdpCommunicationsServer"/> and <see cref="ISocketFactory"/> implementations for this platform.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification="Can't expose along exception paths here (exceptions should be very rare anyway, and probably fatal too) and we shouldn't dipose the items we pass to base in any other case.")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification="Can't expose along exception paths here (exceptions should be very rare anyway, and probably fatal too) and we shouldn't dipose the items we pass to base in any other case.")]
|
||||||
public SsdpDeviceLocator(ISocketFactory socketFactory, ITimerFactory timerFacatory) : base(new SsdpCommunicationsServer(socketFactory), timerFacatory)
|
public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFacatory) : base(communicationsServer, timerFacatory)
|
||||||
{
|
{
|
||||||
// This is not the problem you are looking for;
|
// This is not the problem you are looking for;
|
||||||
// Yes, this is poor man's dependency injection which some call an anti-pattern.
|
// Yes, this is poor man's dependency injection which some call an anti-pattern.
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Threading;
|
using MediaBrowser.Model.Threading;
|
||||||
|
using RSSDP;
|
||||||
|
|
||||||
namespace Rssdp.Infrastructure
|
namespace Rssdp.Infrastructure
|
||||||
{
|
{
|
||||||
|
@ -28,14 +29,6 @@ namespace Rssdp.Infrastructure
|
||||||
private ITimer _ExpireCachedDevicesTimer;
|
private ITimer _ExpireCachedDevicesTimer;
|
||||||
private ITimerFactory _timerFactory;
|
private ITimerFactory _timerFactory;
|
||||||
|
|
||||||
private const string HttpURequestMessageFormat = @"{0} * HTTP/1.1
|
|
||||||
HOST: {1}:{2}
|
|
||||||
MAN: ""{3}""
|
|
||||||
MX: {5}
|
|
||||||
ST: {4}
|
|
||||||
|
|
||||||
";
|
|
||||||
|
|
||||||
private static readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4);
|
private static readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4);
|
||||||
private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);
|
private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
@ -166,21 +159,16 @@ ST: {4}
|
||||||
if (searchWaitTime > TimeSpan.Zero)
|
if (searchWaitTime > TimeSpan.Zero)
|
||||||
await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime)).ConfigureAwait(false);
|
await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime)).ConfigureAwait(false);
|
||||||
|
|
||||||
await Task.Run(() =>
|
|
||||||
{
|
|
||||||
lock (_SearchResultsSynchroniser)
|
lock (_SearchResultsSynchroniser)
|
||||||
{
|
{
|
||||||
foreach (var device in GetUnexpiredDevices().Where((d) => NotificationTypeMatchesFilter(d)))
|
foreach (var device in GetUnexpiredDevices().Where(NotificationTypeMatchesFilter))
|
||||||
{
|
{
|
||||||
if (this.IsDisposed) return;
|
|
||||||
|
|
||||||
DeviceFound(device, false);
|
DeviceFound(device, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (searchWaitTime != TimeSpan.Zero)
|
if (searchWaitTime != TimeSpan.Zero)
|
||||||
await Task.Delay(searchWaitTime);
|
await Task.Delay(searchWaitTime).ConfigureAwait(false);
|
||||||
|
|
||||||
IEnumerable<DiscoveredSsdpDevice> retVal = null;
|
IEnumerable<DiscoveredSsdpDevice> retVal = null;
|
||||||
|
|
||||||
|
@ -192,7 +180,7 @@ ST: {4}
|
||||||
_SearchResults = null;
|
_SearchResults = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var expireTask = RemoveExpiredDevicesFromCacheAsync();
|
RemoveExpiredDevicesFromCache();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -417,25 +405,27 @@ ST: {4}
|
||||||
|
|
||||||
#region Network Message Processing
|
#region Network Message Processing
|
||||||
|
|
||||||
private static byte[] BuildDiscoverMessage(string serviceType, TimeSpan mxValue)
|
|
||||||
{
|
|
||||||
return System.Text.UTF8Encoding.UTF8.GetBytes(
|
|
||||||
String.Format(HttpURequestMessageFormat,
|
|
||||||
SsdpConstants.MSearchMethod,
|
|
||||||
SsdpConstants.MulticastLocalAdminAddress,
|
|
||||||
SsdpConstants.MulticastPort,
|
|
||||||
SsdpConstants.SsdpDiscoverMessage,
|
|
||||||
serviceType,
|
|
||||||
mxValue.TotalSeconds
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue)
|
private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue)
|
||||||
{
|
{
|
||||||
var broadcastMessage = BuildDiscoverMessage(serviceType, mxValue);
|
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
return _CommunicationsServer.SendMulticastMessage(broadcastMessage);
|
values["HOST"] = "239.255.255.250:1900";
|
||||||
|
values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2";
|
||||||
|
//values["X-EMBY-SERVERID"] = _appHost.SystemId;
|
||||||
|
|
||||||
|
values["MAN"] = "\"ssdp:discover\"";
|
||||||
|
|
||||||
|
// Search target
|
||||||
|
values["ST"] = "ssdp:all";
|
||||||
|
|
||||||
|
// Seconds to delay response
|
||||||
|
values["MX"] = "3";
|
||||||
|
|
||||||
|
var header = "M-SEARCH * HTTP/1.1";
|
||||||
|
|
||||||
|
var message = SsdpHelper.BuildMessage(header, values);
|
||||||
|
|
||||||
|
return _CommunicationsServer.SendMulticastMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessSearchResponseMessage(HttpResponseMessage message)
|
private void ProcessSearchResponseMessage(HttpResponseMessage message)
|
||||||
|
@ -608,19 +598,11 @@ ST: {4}
|
||||||
|
|
||||||
#region Expiry and Device Removal
|
#region Expiry and Device Removal
|
||||||
|
|
||||||
private Task RemoveExpiredDevicesFromCacheAsync()
|
|
||||||
{
|
|
||||||
return Task.Run(() =>
|
|
||||||
{
|
|
||||||
RemoveExpiredDevicesFromCache();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveExpiredDevicesFromCache()
|
private void RemoveExpiredDevicesFromCache()
|
||||||
{
|
{
|
||||||
if (this.IsDisposed) return;
|
if (this.IsDisposed) return;
|
||||||
|
|
||||||
IEnumerable<DiscoveredSsdpDevice> expiredDevices = null;
|
DiscoveredSsdpDevice[] expiredDevices = null;
|
||||||
lock (_Devices)
|
lock (_Devices)
|
||||||
{
|
{
|
||||||
expiredDevices = (from device in _Devices where device.IsExpired() select device).ToArray();
|
expiredDevices = (from device in _Devices where device.IsExpired() select device).ToArray();
|
||||||
|
|
|
@ -26,8 +26,8 @@ namespace Rssdp
|
||||||
/// <para>Uses the default <see cref="ISsdpCommunicationsServer"/> implementation and network settings for Windows and the SSDP specification.</para>
|
/// <para>Uses the default <see cref="ISsdpCommunicationsServer"/> implementation and network settings for Windows and the SSDP specification.</para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No way to do this here, and we don't want to dispose it except in the (rare) case of an exception anyway.")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No way to do this here, and we don't want to dispose it except in the (rare) case of an exception anyway.")]
|
||||||
public SsdpDevicePublisher(ISocketFactory socketFactory, ITimerFactory timerFactory, string osName, string osVersion)
|
public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFactory, string osName, string osVersion)
|
||||||
: base(new SsdpCommunicationsServer(socketFactory), timerFactory, osName, osVersion)
|
: base(communicationsServer, timerFactory, osName, osVersion)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Threading;
|
using MediaBrowser.Model.Threading;
|
||||||
|
using RSSDP;
|
||||||
|
|
||||||
namespace Rssdp.Infrastructure
|
namespace Rssdp.Infrastructure
|
||||||
{
|
{
|
||||||
|
@ -344,7 +345,7 @@ namespace Rssdp.Infrastructure
|
||||||
values["USN"] = uniqueServiceName;
|
values["USN"] = uniqueServiceName;
|
||||||
values["LOCATION"] = rootDevice.Location.ToString();
|
values["LOCATION"] = rootDevice.Location.ToString();
|
||||||
|
|
||||||
var message = BuildMessage(header, values);
|
var message = SsdpHelper.BuildMessage(header, values);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -384,10 +385,7 @@ namespace Rssdp.Infrastructure
|
||||||
return isDuplicateRequest;
|
return isDuplicateRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable avoids compiler warning, but value is otherwise not required.")]
|
|
||||||
private void CleanUpRecentSearchRequestsAsync()
|
private void CleanUpRecentSearchRequestsAsync()
|
||||||
{
|
|
||||||
var t = Task.Run(() =>
|
|
||||||
{
|
{
|
||||||
lock (_RecentSearchRequests)
|
lock (_RecentSearchRequests)
|
||||||
{
|
{
|
||||||
|
@ -396,7 +394,6 @@ namespace Rssdp.Infrastructure
|
||||||
_RecentSearchRequests.Remove(requestKey);
|
_RecentSearchRequests.Remove(requestKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -481,31 +478,13 @@ namespace Rssdp.Infrastructure
|
||||||
values["NT"] = notificationType;
|
values["NT"] = notificationType;
|
||||||
values["USN"] = uniqueServiceName;
|
values["USN"] = uniqueServiceName;
|
||||||
|
|
||||||
var message = BuildMessage(header, values);
|
var message = SsdpHelper.BuildMessage(header, values);
|
||||||
|
|
||||||
_CommsServer.SendMulticastMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message));
|
_CommsServer.SendMulticastMessage(message);
|
||||||
|
|
||||||
WriteTrace(String.Format("Sent alive notification"), device);
|
WriteTrace(String.Format("Sent alive notification"), device);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildMessage(string header, Dictionary<string, string> values)
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
|
|
||||||
const string argFormat = "{0}: {1}\r\n";
|
|
||||||
|
|
||||||
builder.AppendFormat("{0}\r\n", header);
|
|
||||||
|
|
||||||
foreach (var pair in values)
|
|
||||||
{
|
|
||||||
builder.AppendFormat(argFormat, pair.Key, pair.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Append("\r\n");
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ByeBye
|
#region ByeBye
|
||||||
|
@ -543,9 +522,9 @@ namespace Rssdp.Infrastructure
|
||||||
values["NT"] = notificationType;
|
values["NT"] = notificationType;
|
||||||
values["USN"] = uniqueServiceName;
|
values["USN"] = uniqueServiceName;
|
||||||
|
|
||||||
var message = BuildMessage(header, values);
|
var message = SsdpHelper.BuildMessage(header, values);
|
||||||
|
|
||||||
return _CommsServer.SendMulticastMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message));
|
return _CommsServer.SendMulticastMessage(message);
|
||||||
|
|
||||||
//WriteTrace(String.Format("Sent byebye notification"), device);
|
//WriteTrace(String.Format("Sent byebye notification"), device);
|
||||||
}
|
}
|
||||||
|
@ -686,7 +665,7 @@ namespace Rssdp.Infrastructure
|
||||||
{
|
{
|
||||||
if (this.IsDisposed) return;
|
if (this.IsDisposed) return;
|
||||||
|
|
||||||
if (e.Message.Method.Method == SsdpConstants.MSearchMethod)
|
if (string.Equals(e.Message.Method.Method, SsdpConstants.MSearchMethod, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
//According to SSDP/UPnP spec, ignore message if missing these headers.
|
//According to SSDP/UPnP spec, ignore message if missing these headers.
|
||||||
// Edit: But some devices do it anyway
|
// Edit: But some devices do it anyway
|
||||||
|
|
88
RSSDP/SsdpHelper.cs
Normal file
88
RSSDP/SsdpHelper.cs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
|
||||||
|
namespace RSSDP
|
||||||
|
{
|
||||||
|
public class SsdpHelper
|
||||||
|
{
|
||||||
|
private readonly ITextEncoding _encoding;
|
||||||
|
|
||||||
|
public SsdpHelper(ITextEncoding encoding)
|
||||||
|
{
|
||||||
|
_encoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SsdpMessageInfo ParseSsdpResponse(byte[] data)
|
||||||
|
{
|
||||||
|
using (var ms = new MemoryStream(data))
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(ms, _encoding.GetASCIIEncoding()))
|
||||||
|
{
|
||||||
|
var proto = (reader.ReadLine() ?? string.Empty).Trim();
|
||||||
|
var method = proto.Split(new[] { ' ' }, 2)[0];
|
||||||
|
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
|
||||||
|
{
|
||||||
|
line = line.Trim();
|
||||||
|
if (string.IsNullOrEmpty(line))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var parts = line.Split(new[] { ':' }, 2);
|
||||||
|
|
||||||
|
if (parts.Length >= 2)
|
||||||
|
{
|
||||||
|
headers[parts[0]] = parts[1].Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SsdpMessageInfo
|
||||||
|
{
|
||||||
|
Method = method,
|
||||||
|
Headers = headers,
|
||||||
|
Message = data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildMessage(string header, Dictionary<string, string> values)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
const string argFormat = "{0}: {1}\r\n";
|
||||||
|
|
||||||
|
builder.AppendFormat("{0}\r\n", header);
|
||||||
|
|
||||||
|
foreach (var pair in values)
|
||||||
|
{
|
||||||
|
builder.AppendFormat(argFormat, pair.Key, pair.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append("\r\n");
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SsdpMessageInfo
|
||||||
|
{
|
||||||
|
public string Method { get; set; }
|
||||||
|
|
||||||
|
public IpEndPointInfo EndPoint { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> Headers { get; set; }
|
||||||
|
|
||||||
|
public IpEndPointInfo LocalEndPoint { get; set; }
|
||||||
|
public byte[] Message { get; set; }
|
||||||
|
|
||||||
|
public SsdpMessageInfo()
|
||||||
|
{
|
||||||
|
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -413,8 +413,8 @@ namespace SocketHttpListener.Net
|
||||||
* HttpStatusCode.InternalServerError 500
|
* HttpStatusCode.InternalServerError 500
|
||||||
* HttpStatusCode.ServiceUnavailable 503
|
* HttpStatusCode.ServiceUnavailable 503
|
||||||
*/
|
*/
|
||||||
bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
|
bool conn_close = (status_code == 408 || status_code == 411 ||
|
||||||
status_code == 413 || status_code == 414 || status_code == 500 ||
|
status_code == 413 || status_code == 414 ||
|
||||||
status_code == 503);
|
status_code == 503);
|
||||||
|
|
||||||
if (conn_close == false)
|
if (conn_close == false)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user