Merge branch 'beta' of https://github.com/MediaBrowser/Emby into beta

This commit is contained in:
Luke Pulverenti 2016-11-14 15:18:01 -05:00
commit 6d40ab7e1c
16 changed files with 179 additions and 126 deletions

View File

@ -61,7 +61,7 @@ namespace Emby.Common.Implementations.IO
{ {
get get
{ {
return Path.DirectorySeparatorChar; return Path.PathSeparator;
} }
} }

View File

@ -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));

View File

@ -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);

View File

@ -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()

View File

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

View File

@ -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");

View File

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

View File

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

View File

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

View File

@ -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)
{ {

View File

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

View File

@ -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();

View File

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

View File

@ -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
View 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);
}
}
}

View File

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