Browse Source

Merge pull request #2289 from MediaBrowser/dev

Dev
Luke 8 years ago
parent
commit
588c06fca0

+ 1 - 1
Emby.Common.Implementations/IO/ManagedFileSystem.cs

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

+ 1 - 0
Emby.Common.Implementations/Net/SocketFactory.cs

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

+ 2 - 9
Emby.Common.Implementations/Net/UdpSocket.cs

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

+ 23 - 7
Emby.Dlna/Main/DlnaEntryPoint.cs

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

+ 8 - 12
Emby.Dlna/Ssdp/DeviceDiscovery.cs

@@ -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.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
+            _deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
+            _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)

+ 3 - 4
Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs

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

+ 1 - 1
MediaBrowser.Server.Mono/Program.cs

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

+ 1 - 2
RSSDP/ISsdpCommunicationsServer.cs

@@ -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(byte[] messageData);
+        Task SendMulticastMessage(string message);
 
 
         #endregion
         #endregion
 
 

+ 1 - 0
RSSDP/RSSDP.csproj

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

+ 8 - 9
RSSDP/SsdpCommunicationsServer.cs

@@ -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>
-        /// <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)
+        public async Task SendMulticastMessage(string message)
         {
         {
-            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);
-                            var processTask = Task.Run(processWork);
+                            ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint);
                         }
                         }
                     }
                     }
                     catch (ObjectDisposedException)
                     catch (ObjectDisposedException)
@@ -330,7 +327,9 @@ namespace Rssdp.Infrastructure
                 lock (_SendSocketSynchroniser)
                 lock (_SendSocketSynchroniser)
                 {
                 {
                     if (_SendSocket == null)
                     if (_SendSocket == null)
+                    {
                         _SendSocket = CreateSocketAndListenForResponsesAsync();
                         _SendSocket = CreateSocketAndListenForResponsesAsync();
+                    }
                 }
                 }
             }
             }
         }
         }

+ 1 - 1
RSSDP/SsdpDeviceLocator.cs

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

+ 26 - 44
RSSDP/SsdpDeviceLocatorBase.cs

@@ -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(NotificationTypeMatchesFilter))
                 {
                 {
-                    foreach (var device in GetUnexpiredDevices().Where((d) => NotificationTypeMatchesFilter(d)))
-                    {
-                        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();

+ 2 - 2
RSSDP/SsdpDevicePublisher.cs

@@ -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)
-			: base(new SsdpCommunicationsServer(socketFactory), timerFactory, osName, osVersion)
+		public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFactory, string osName, string osVersion)
+			: base(communicationsServer, timerFactory, osName, osVersion)
 		{
 		{
 
 
 		}
 		}

+ 11 - 32
RSSDP/SsdpDevicePublisherBase.cs

@@ -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,19 +385,15 @@ 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)
+                foreach (var requestKey in (from r in _RecentSearchRequests where r.Value.IsOld() select r.Key).ToArray())
                 {
                 {
-                    foreach (var requestKey in (from r in _RecentSearchRequests where r.Value.IsOld() select r.Key).ToArray())
-                    {
-                        _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 - 0
RSSDP/SsdpHelper.cs

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

+ 2 - 2
SocketHttpListener.Portable/Net/HttpListenerResponse.cs

@@ -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 ||
-                    status_code == 413 || status_code == 414 || status_code == 500 ||
+            bool conn_close = (status_code == 408 || status_code == 411 ||
+                    status_code == 413 || status_code == 414 ||
                     status_code == 503);
                     status_code == 503);
 
 
             if (conn_close == false)
             if (conn_close == false)