Browse Source

add cancellation to socket methods

Luke Pulverenti 8 years ago
parent
commit
d218dbd2a1

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

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Net;
 using System.Net;
 using System.Net.Sockets;
 using System.Net.Sockets;
 using System.Security;
 using System.Security;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Emby.Common.Implementations.Networking;
 using Emby.Common.Implementations.Networking;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
@@ -56,7 +57,7 @@ namespace Emby.Common.Implementations.Net
             state.TaskCompletionSource = tcs;
             state.TaskCompletionSource = tcs;
 
 
 #if NETSTANDARD1_6
 #if NETSTANDARD1_6
-            _Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer),SocketFlags.None, state.RemoteEndPoint)
+            _Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
                 .ContinueWith((task, asyncState) =>
                 .ContinueWith((task, asyncState) =>
                 {
                 {
                     if (task.Status != TaskStatus.Faulted)
                     if (task.Status != TaskStatus.Faulted)
@@ -73,7 +74,7 @@ namespace Emby.Common.Implementations.Net
             return tcs.Task;
             return tcs.Task;
         }
         }
 
 
-        public Task SendAsync(byte[] buffer, int size, IpEndPointInfo endPoint)
+        public Task SendAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken)
         {
         {
             ThrowIfDisposed();
             ThrowIfDisposed();
 
 
@@ -91,6 +92,8 @@ namespace Emby.Common.Implementations.Net
                 buffer = copy;
                 buffer = copy;
             }
             }
 
 
+            cancellationToken.ThrowIfCancellationRequested();
+
             _Socket.SendTo(buffer, ipEndPoint);
             _Socket.SendTo(buffer, ipEndPoint);
             return Task.FromResult(true);
             return Task.FromResult(true);
 #else
 #else
@@ -100,6 +103,11 @@ namespace Emby.Common.Implementations.Net
             {
             {
                 _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result =>
                 _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result =>
                 {
                 {
+                    if (cancellationToken.IsCancellationRequested)
+                    {
+                        taskSource.TrySetCanceled();
+                        return;
+                    }
                     try
                     try
                     {
                     {
                         _Socket.EndSend(result);
                         _Socket.EndSend(result);

+ 9 - 5
Emby.Dlna/Ssdp/DeviceDiscovery.cs

@@ -75,16 +75,20 @@ namespace Emby.Dlna.Ssdp
                         // Enable listening for notifications (optional)
                         // Enable listening for notifications (optional)
                         _deviceLocator.StartListeningForNotifications();
                         _deviceLocator.StartListeningForNotifications();
 
 
-                        await _deviceLocator.SearchAsync().ConfigureAwait(false);
+                        await _deviceLocator.SearchAsync(_tokenSource.Token).ConfigureAwait(false);
+
+                        var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
+
+                        await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
+                    }
+                    catch (OperationCanceledException)
+                    {
+
                     }
                     }
                     catch (Exception ex)
                     catch (Exception ex)
                     {
                     {
                         _logger.ErrorException("Error searching for devices", ex);
                         _logger.ErrorException("Error searching for devices", ex);
                     }
                     }
-
-                    var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
-
-                    await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
                 }
                 }
 
 
             }, CancellationToken.None, TaskCreationOptions.LongRunning);
             }, CancellationToken.None, TaskCreationOptions.LongRunning);

+ 2 - 1
Emby.Server.Implementations/Udp/UdpServer.cs

@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
@@ -246,7 +247,7 @@ namespace Emby.Server.Implementations.Udp
 
 
             try
             try
             {
             {
-                await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint).ConfigureAwait(false);
+                await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
 
 
                 _logger.Info("Udp message sent to {0}", remoteEndPoint);
                 _logger.Info("Udp message sent to {0}", remoteEndPoint);
             }
             }

+ 2 - 1
MediaBrowser.Model/Net/IUdpSocket.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Model.Net
 namespace MediaBrowser.Model.Net
@@ -22,6 +23,6 @@ namespace MediaBrowser.Model.Net
         /// <summary>
         /// <summary>
         /// Sends a UDP message to a particular end point (uni or multicast).
         /// Sends a UDP message to a particular end point (uni or multicast).
         /// </summary>
         /// </summary>
-        Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint);
+        Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken);
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs

@@ -124,7 +124,7 @@ namespace MediaBrowser.Providers.MediaInfo
         {
         {
             get
             get
             {
             {
-                return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami", ".txt" };
+                return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami" };
             }
             }
         }
         }
 
 

+ 4 - 7
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -340,7 +340,7 @@ namespace MediaBrowser.WebDashboard.Api
         {
         {
             var mode = request.Mode;
             var mode = request.Mode;
 
 
-            var path = string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ?
+            var path = !string.IsNullOrWhiteSpace(mode) ?
                 Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump")
                 Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump")
                 : "C:\\dev\\emby-web-mobile\\src";
                 : "C:\\dev\\emby-web-mobile\\src";
 
 
@@ -364,14 +364,11 @@ namespace MediaBrowser.WebDashboard.Api
             // Try to trim the output size a bit
             // Try to trim the output size a bit
             var bowerPath = Path.Combine(path, "bower_components");
             var bowerPath = Path.Combine(path, "bower_components");
 
 
-            if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
-            {
-                DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto");
-            }
-
-            if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
+            if (!string.IsNullOrWhiteSpace(mode))
             {
             {
                 // Delete things that are unneeded in an attempt to keep the output as trim as possible
                 // Delete things that are unneeded in an attempt to keep the output as trim as possible
+
+                DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto");
                 _fileSystem.DeleteDirectory(Path.Combine(path, "css", "images", "tour"), true);
                 _fileSystem.DeleteDirectory(Path.Combine(path, "css", "images", "tour"), true);
             }
             }
 
 

+ 6 - 5
MediaBrowser.WebDashboard/Api/PackageCreator.cs

@@ -142,7 +142,7 @@ namespace MediaBrowser.WebDashboard.Api
 
 
                     html = Encoding.UTF8.GetString(originalBytes, 0, originalBytes.Length);
                     html = Encoding.UTF8.GetString(originalBytes, 0, originalBytes.Length);
 
 
-                    if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
+                    if (!string.IsNullOrWhiteSpace(mode))
                     {
                     {
                     }
                     }
                     else if (!string.IsNullOrWhiteSpace(path) && !string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase))
                     else if (!string.IsNullOrWhiteSpace(path) && !string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase))
@@ -208,7 +208,8 @@ namespace MediaBrowser.WebDashboard.Api
         {
         {
             var sb = new StringBuilder();
             var sb = new StringBuilder();
 
 
-            if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase))
             {
             {
                 sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem: ws: wss:;\">");
                 sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem: ws: wss:;\">");
             }
             }
@@ -257,7 +258,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
         private string GetCommonCss(string mode, string version)
         private string GetCommonCss(string mode, string version)
         {
         {
-            var versionString = !string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? "?v=" + version : string.Empty;
+            var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty;
 
 
             var files = new[]
             var files = new[]
                             {
                             {
@@ -288,14 +289,14 @@ namespace MediaBrowser.WebDashboard.Api
                 builder.AppendFormat("window.appMode='{0}';", mode);
                 builder.AppendFormat("window.appMode='{0}';", mode);
             }
             }
 
 
-            if (!string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
+            if (string.IsNullOrWhiteSpace(mode))
             {
             {
                 builder.AppendFormat("window.dashboardVersion='{0}';", version);
                 builder.AppendFormat("window.dashboardVersion='{0}';", version);
             }
             }
 
 
             builder.Append("</script>");
             builder.Append("</script>");
 
 
-            var versionString = !string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? "?v=" + version : string.Empty;
+            var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty;
 
 
             var files = new List<string>();
             var files = new List<string>();
 
 

+ 3 - 5
RSSDP/ISsdpCommunicationsServer.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
 
 
@@ -44,15 +45,12 @@ namespace Rssdp.Infrastructure
         /// <summary>
         /// <summary>
         /// Sends a message to a particular address (uni or multicast) and port.
         /// Sends a message to a particular address (uni or multicast) and port.
         /// </summary>
         /// </summary>
-        /// <param name="messageData">A byte array containing the data to send.</param>
-        /// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
-        /// <param name="fromLocalIpAddress">A <see cref="IpEndPointInfo"/> The local ip address to send from, or .Any if sending from all available</param>
-        Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress);
+        Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Sends a message to the SSDP multicast address and port.
         /// Sends a message to the SSDP multicast address and port.
         /// </summary>
         /// </summary>
-        Task SendMulticastMessage(string message);
+        Task SendMulticastMessage(string message, CancellationToken cancellationToken);
 
 
         #endregion
         #endregion
 
 

+ 0 - 5
RSSDP/ISsdpDeviceLocator.cs

@@ -58,11 +58,6 @@ namespace Rssdp.Infrastructure
 			set;
 			set;
 		}
 		}
 
 
-		/// <summary>
-		/// Returns a boolean indicating whether or not a search is currently active.
-		/// </summary>
-		bool IsSearching { get; }
-
 		#endregion
 		#endregion
 
 
 		#region Methods
 		#region Methods

+ 13 - 15
RSSDP/SsdpCommunicationsServer.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Net;
 using System.Net;
 using System.Net.Http;
 using System.Net.Http;
 using System.Text;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
@@ -149,12 +150,7 @@ namespace Rssdp.Infrastructure
         /// <summary>
         /// <summary>
         /// Sends a message to a particular address (uni or multicast) and port.
         /// Sends a message to a particular address (uni or multicast) and port.
         /// </summary>
         /// </summary>
-        /// <param name="messageData">A byte array containing the data to send.</param>
-        /// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
-        /// <param name="fromLocalIpAddress">A <see cref="IpEndPointInfo"/> The local ip address to send from, or .Any if sending from all available</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 SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress)
+        public async Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken)
         {
         {
             if (messageData == null) throw new ArgumentNullException("messageData");
             if (messageData == null) throw new ArgumentNullException("messageData");
 
 
@@ -170,18 +166,18 @@ namespace Rssdp.Infrastructure
             // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP.
             // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP.
             for (var i = 0; i < SsdpConstants.UdpResendCount; i++)
             for (var i = 0; i < SsdpConstants.UdpResendCount; i++)
             {
             {
-                var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination)).ToArray();
+                var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination, cancellationToken)).ToArray();
                 await Task.WhenAll(tasks).ConfigureAwait(false);
                 await Task.WhenAll(tasks).ConfigureAwait(false);
 
 
-                await Task.Delay(100).ConfigureAwait(false);
+                await Task.Delay(100, cancellationToken).ConfigureAwait(false);
             }
             }
         }
         }
 
 
-        private async Task SendFromSocket(IUdpSocket socket, byte[] messageData, IpEndPointInfo destination)
+        private async Task SendFromSocket(IUdpSocket socket, byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken)
         {
         {
             try
             try
             {
             {
-                await socket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false);
+                await socket.SendAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false);
             }
             }
             catch (ObjectDisposedException)
             catch (ObjectDisposedException)
             {
             {
@@ -230,7 +226,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>
-        public async Task SendMulticastMessage(string message)
+        public async Task SendMulticastMessage(string message, CancellationToken cancellationToken)
         {
         {
             if (message == null) throw new ArgumentNullException("messageData");
             if (message == null) throw new ArgumentNullException("messageData");
 
 
@@ -238,6 +234,8 @@ namespace Rssdp.Infrastructure
 
 
             ThrowIfDisposed();
             ThrowIfDisposed();
 
 
+            cancellationToken.ThrowIfCancellationRequested();
+
             EnsureSendSocketCreated();
             EnsureSendSocketCreated();
 
 
             // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP.
             // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP.
@@ -251,9 +249,9 @@ namespace Rssdp.Infrastructure
                     },
                     },
                     Port = SsdpConstants.MulticastPort
                     Port = SsdpConstants.MulticastPort
 
 
-                }).ConfigureAwait(false);
+                }, cancellationToken).ConfigureAwait(false);
 
 
-                await Task.Delay(100).ConfigureAwait(false);
+                await Task.Delay(100, cancellationToken).ConfigureAwait(false);
             }
             }
         }
         }
 
 
@@ -334,7 +332,7 @@ namespace Rssdp.Infrastructure
 
 
         #region Private Methods
         #region Private Methods
 
 
-        private async Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination)
+        private async Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken)
         {
         {
             var sockets = _sendSockets;
             var sockets = _sendSockets;
             if (sockets != null)
             if (sockets != null)
@@ -343,7 +341,7 @@ namespace Rssdp.Infrastructure
 
 
                 foreach (var socket in sockets)
                 foreach (var socket in sockets)
                 {
                 {
-                    await socket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false);
+                    await socket.SendAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false);
                 }
                 }
             }
             }
 
 

+ 10 - 38
RSSDP/SsdpDeviceLocatorBase.cs

@@ -16,7 +16,7 @@ namespace Rssdp.Infrastructure
     /// <summary>
     /// <summary>
     /// Allows you to search the network for a particular device, device types, or UPnP service types. Also listenings for broadcast notifications of device availability and raises events to indicate changes in status.
     /// Allows you to search the network for a particular device, device types, or UPnP service types. Also listenings for broadcast notifications of device availability and raises events to indicate changes in status.
     /// </summary>
     /// </summary>
-    public abstract class SsdpDeviceLocatorBase : DisposableManagedObjectBase, ISsdpDeviceLocator
+    public abstract class SsdpDeviceLocatorBase : DisposableManagedObjectBase
     {
     {
 
 
         #region Fields & Constants
         #region Fields & Constants
@@ -96,9 +96,9 @@ namespace Rssdp.Infrastructure
         /// Performs a search for all devices using the default search timeout.
         /// Performs a search for all devices using the default search timeout.
         /// </summary>
         /// </summary>
         /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
         /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
-        public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync()
+        public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(CancellationToken cancellationToken)
         {
         {
-            return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, DefaultSearchWaitTime);
+            return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, DefaultSearchWaitTime, cancellationToken);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -114,7 +114,7 @@ namespace Rssdp.Infrastructure
         /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
         /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
         public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget)
         public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget)
         {
         {
-            return SearchAsync(searchTarget, DefaultSearchWaitTime);
+            return SearchAsync(searchTarget, DefaultSearchWaitTime, CancellationToken.None);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -124,27 +124,10 @@ namespace Rssdp.Infrastructure
         /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
         /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
         public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(TimeSpan searchWaitTime)
         public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(TimeSpan searchWaitTime)
         {
         {
-            return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, searchWaitTime);
+            return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, searchWaitTime, CancellationToken.None);
         }
         }
 
 
-        /// <summary>
-        /// Performs a search for the specified search target (criteria) and search timeout.
-        /// </summary>
-        /// <param name="searchTarget">The criteria for the search. Value can be;
-        /// <list type="table">
-        /// <item><term>Root devices</term><description>upnp:rootdevice</description></item>
-        /// <item><term>Specific device by UUID</term><description>uuid:&lt;device uuid&gt;</description></item>
-        /// <item><term>Device type</term><description>A device namespace and type in format of urn:&lt;device namespace&gt;:device:&lt;device type&gt;:&lt;device version&gt; i.e urn:schemas-upnp-org:device:Basic:1</description></item>
-        /// <item><term>Service type</term><description>A service namespace and type in format of urn:&lt;service namespace&gt;:service:&lt;servicetype&gt;:&lt;service version&gt; i.e urn:my-namespace:service:MyCustomService:1</description></item>
-        /// </list>
-        /// </param>
-        /// <param name="searchWaitTime">The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 seconds is recommended by the UPnP 1.1 specification, this method requires the value be greater 1 second if it is not zero. Specify TimeSpan.Zero to return only devices already in the cache.</param>
-        /// <remarks>
-        /// <para>By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implemetning these services if you know the service type.</para>
-        /// </remarks>
-        /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
-        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "expireTask", Justification = "Task is not actually required, but capturing to local variable suppresses compiler warning")]
-        public async Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget, TimeSpan searchWaitTime)
+        public async Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget, TimeSpan searchWaitTime, CancellationToken cancellationToken)
         {
         {
             if (searchTarget == null) throw new ArgumentNullException("searchTarget");
             if (searchTarget == null) throw new ArgumentNullException("searchTarget");
             if (searchTarget.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", "searchTarget");
             if (searchTarget.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", "searchTarget");
@@ -158,7 +141,7 @@ namespace Rssdp.Infrastructure
 
 
             // If searchWaitTime == 0 then we are only going to report unexpired cached items, not actually do a search.
             // If searchWaitTime == 0 then we are only going to report unexpired cached items, not actually do a search.
             if (searchWaitTime > TimeSpan.Zero)
             if (searchWaitTime > TimeSpan.Zero)
-                await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime)).ConfigureAwait(false);
+                await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime), cancellationToken).ConfigureAwait(false);
 
 
             lock (_SearchResultsSynchroniser)
             lock (_SearchResultsSynchroniser)
             {
             {
@@ -169,7 +152,7 @@ namespace Rssdp.Infrastructure
             }
             }
 
 
             if (searchWaitTime != TimeSpan.Zero)
             if (searchWaitTime != TimeSpan.Zero)
-                await Task.Delay(searchWaitTime).ConfigureAwait(false);
+                await Task.Delay(searchWaitTime, cancellationToken).ConfigureAwait(false);
 
 
             IEnumerable<DiscoveredSsdpDevice> retVal = null;
             IEnumerable<DiscoveredSsdpDevice> retVal = null;
 
 
@@ -270,17 +253,6 @@ namespace Rssdp.Infrastructure
 
 
         #region Public Properties
         #region Public Properties
 
 
-        /// <summary>
-        /// Returns a boolean indicating whether or not a search is currently in progress.
-        /// </summary>
-        /// <remarks>
-        /// <para>Only one search can be performed at a time, per <see cref="SsdpDeviceLocatorBase"/> instance.</para>
-        /// </remarks>
-        public bool IsSearching
-        {
-            get { return _SearchResults != null; }
-        }
-
         /// <summary>
         /// <summary>
         /// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the <see cref="ISsdpDeviceLocator.DeviceAvailable"/> or <see cref="ISsdpDeviceLocator.DeviceUnavailable"/> events.
         /// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the <see cref="ISsdpDeviceLocator.DeviceAvailable"/> or <see cref="ISsdpDeviceLocator.DeviceUnavailable"/> events.
         /// </summary>
         /// </summary>
@@ -407,7 +379,7 @@ namespace Rssdp.Infrastructure
 
 
         #region Network Message Processing
         #region Network Message Processing
 
 
-        private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue)
+        private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue, CancellationToken cancellationToken)
         {
         {
             var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
 
@@ -427,7 +399,7 @@ namespace Rssdp.Infrastructure
 
 
             var message = SsdpHelper.BuildMessage(header, values);
             var message = SsdpHelper.BuildMessage(header, values);
 
 
-            return _CommunicationsServer.SendMulticastMessage(message);
+            return _CommunicationsServer.SendMulticastMessage(message, cancellationToken);
         }
         }
 
 
         private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress)
         private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress)

+ 32 - 31
RSSDP/SsdpDevicePublisherBase.cs

@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
 using System.Net.Http;
 using System.Net.Http;
 using System.Text;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Threading;
 using MediaBrowser.Model.Threading;
@@ -122,7 +123,7 @@ namespace Rssdp.Infrastructure
 
 
                 SetRebroadcastAliveNotificationsTimer(minCacheTime);
                 SetRebroadcastAliveNotificationsTimer(minCacheTime);
 
 
-                SendAliveNotifications(device, true);
+                SendAliveNotifications(device, true, CancellationToken.None);
             }
             }
         }
         }
 
 
@@ -161,7 +162,7 @@ namespace Rssdp.Infrastructure
 
 
                 WriteTrace("Device Removed", device);
                 WriteTrace("Device Removed", device);
 
 
-                await SendByeByeNotifications(device, true).ConfigureAwait(false);
+                await SendByeByeNotifications(device, true, CancellationToken.None).ConfigureAwait(false);
 
 
                 SetRebroadcastAliveNotificationsTimer(minCacheTime);
                 SetRebroadcastAliveNotificationsTimer(minCacheTime);
             }
             }
@@ -237,7 +238,7 @@ namespace Rssdp.Infrastructure
 
 
         #region Search Related Methods
         #region Search Related Methods
 
 
-        private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnlocalIpAddress)
+        private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken)
         {
         {
             if (String.IsNullOrEmpty(searchTarget))
             if (String.IsNullOrEmpty(searchTarget))
             {
             {
@@ -295,7 +296,7 @@ namespace Rssdp.Infrastructure
 
 
                     foreach (var device in deviceList)
                     foreach (var device in deviceList)
                     {
                     {
-                        SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress);
+                        SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken);
                     }
                     }
                 }
                 }
                 else
                 else
@@ -310,19 +311,19 @@ namespace Rssdp.Infrastructure
             return _Devices.Union(_Devices.SelectManyRecursive<SsdpDevice>((d) => d.Devices));
             return _Devices.Union(_Devices.SelectManyRecursive<SsdpDevice>((d) => d.Devices));
         }
         }
 
 
-        private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress)
+        private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken)
         {
         {
             bool isRootDevice = (device as SsdpRootDevice) != null;
             bool isRootDevice = (device as SsdpRootDevice) != null;
             if (isRootDevice)
             if (isRootDevice)
             {
             {
-                SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress);
+                SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress, cancellationToken);
                 if (this.SupportPnpRootDevice)
                 if (this.SupportPnpRootDevice)
-                    SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress);
+                    SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress, cancellationToken);
             }
             }
 
 
-            SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIpAddress);
+            SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIpAddress, cancellationToken);
 
 
-            SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint, receivedOnlocalIpAddress);
+            SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint, receivedOnlocalIpAddress, cancellationToken);
         }
         }
 
 
         private static string GetUsn(string udn, string fullDeviceType)
         private static string GetUsn(string udn, string fullDeviceType)
@@ -330,7 +331,7 @@ namespace Rssdp.Infrastructure
             return String.Format("{0}::{1}", udn, fullDeviceType);
             return String.Format("{0}::{1}", udn, fullDeviceType);
         }
         }
 
 
-        private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress)
+        private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken)
         {
         {
             var rootDevice = device.ToRootDevice();
             var rootDevice = device.ToRootDevice();
 
 
@@ -352,7 +353,7 @@ namespace Rssdp.Infrastructure
 
 
             try
             try
             {
             {
-                await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint, receivedOnlocalIpAddress).ConfigureAwait(false);
+                await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint, receivedOnlocalIpAddress, cancellationToken).ConfigureAwait(false);
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
@@ -427,7 +428,7 @@ namespace Rssdp.Infrastructure
                 {
                 {
                     if (IsDisposed) return;
                     if (IsDisposed) return;
 
 
-                    SendAliveNotifications(device, true);
+                    SendAliveNotifications(device, true, CancellationToken.None);
                 }
                 }
 
 
                 //WriteTrace("Completed Sending Alive Notifications For All Devices");
                 //WriteTrace("Completed Sending Alive Notifications For All Devices");
@@ -445,25 +446,25 @@ namespace Rssdp.Infrastructure
             //}
             //}
         }
         }
 
 
-        private void SendAliveNotifications(SsdpDevice device, bool isRoot)
+        private void SendAliveNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken)
         {
         {
             if (isRoot)
             if (isRoot)
             {
             {
-                SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice));
+                SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken);
                 if (this.SupportPnpRootDevice)
                 if (this.SupportPnpRootDevice)
-                    SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice));
+                    SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), cancellationToken);
             }
             }
 
 
-            SendAliveNotification(device, device.Udn, device.Udn);
-            SendAliveNotification(device, device.FullDeviceType, GetUsn(device.Udn, device.FullDeviceType));
+            SendAliveNotification(device, device.Udn, device.Udn, cancellationToken);
+            SendAliveNotification(device, device.FullDeviceType, GetUsn(device.Udn, device.FullDeviceType), cancellationToken);
 
 
             foreach (var childDevice in device.Devices)
             foreach (var childDevice in device.Devices)
             {
             {
-                SendAliveNotifications(childDevice, false);
+                SendAliveNotifications(childDevice, false, cancellationToken);
             }
             }
         }
         }
 
 
-        private void SendAliveNotification(SsdpDevice device, string notificationType, string uniqueServiceName)
+        private void SendAliveNotification(SsdpDevice device, string notificationType, string uniqueServiceName, CancellationToken cancellationToken)
         {
         {
             var rootDevice = device.ToRootDevice();
             var rootDevice = device.ToRootDevice();
 
 
@@ -483,7 +484,7 @@ namespace Rssdp.Infrastructure
 
 
             var message = SsdpHelper.BuildMessage(header, values);
             var message = SsdpHelper.BuildMessage(header, values);
 
 
-            _CommsServer.SendMulticastMessage(message);
+            _CommsServer.SendMulticastMessage(message, cancellationToken);
 
 
             //WriteTrace(String.Format("Sent alive notification"), device);
             //WriteTrace(String.Format("Sent alive notification"), device);
         }
         }
@@ -492,26 +493,26 @@ namespace Rssdp.Infrastructure
 
 
         #region ByeBye
         #region ByeBye
 
 
-        private async Task SendByeByeNotifications(SsdpDevice device, bool isRoot)
+        private async Task SendByeByeNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken)
         {
         {
             if (isRoot)
             if (isRoot)
             {
             {
-                await SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice)).ConfigureAwait(false);
+                await SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken).ConfigureAwait(false);
                 if (this.SupportPnpRootDevice)
                 if (this.SupportPnpRootDevice)
-                    await SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice")).ConfigureAwait(false); ;
+                    await SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice"), cancellationToken).ConfigureAwait(false); ;
             }
             }
 
 
-            await SendByeByeNotification(device, device.Udn, device.Udn).ConfigureAwait(false); ;
-            await SendByeByeNotification(device, String.Format("urn:{0}", device.FullDeviceType), GetUsn(device.Udn, device.FullDeviceType)).ConfigureAwait(false); ;
+            await SendByeByeNotification(device, device.Udn, device.Udn, cancellationToken).ConfigureAwait(false); ;
+            await SendByeByeNotification(device, String.Format("urn:{0}", device.FullDeviceType), GetUsn(device.Udn, device.FullDeviceType), cancellationToken).ConfigureAwait(false); ;
 
 
             foreach (var childDevice in device.Devices)
             foreach (var childDevice in device.Devices)
             {
             {
-                await SendByeByeNotifications(childDevice, false).ConfigureAwait(false); ;
+                await SendByeByeNotifications(childDevice, false, cancellationToken).ConfigureAwait(false); ;
             }
             }
         }
         }
 
 
         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "byebye", Justification = "Correct value for this type of notification in SSDP.")]
         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "byebye", Justification = "Correct value for this type of notification in SSDP.")]
-        private Task SendByeByeNotification(SsdpDevice device, string notificationType, string uniqueServiceName)
+        private Task SendByeByeNotification(SsdpDevice device, string notificationType, string uniqueServiceName, CancellationToken cancellationToken)
         {
         {
             const string header = "NOTIFY * HTTP/1.1";
             const string header = "NOTIFY * HTTP/1.1";
 
 
@@ -527,7 +528,7 @@ namespace Rssdp.Infrastructure
 
 
             var message = SsdpHelper.BuildMessage(header, values);
             var message = SsdpHelper.BuildMessage(header, values);
 
 
-            return _CommsServer.SendMulticastMessage(message);
+            return _CommsServer.SendMulticastMessage(message, cancellationToken);
 
 
             //WriteTrace(String.Format("Sent byebye notification"), device);
             //WriteTrace(String.Format("Sent byebye notification"), device);
         }
         }
@@ -653,13 +654,13 @@ namespace Rssdp.Infrastructure
 
 
         private void device_DeviceAdded(object sender, DeviceEventArgs e)
         private void device_DeviceAdded(object sender, DeviceEventArgs e)
         {
         {
-            SendAliveNotifications(e.Device, false);
+            SendAliveNotifications(e.Device, false, CancellationToken.None);
             ConnectToDeviceEvents(e.Device);
             ConnectToDeviceEvents(e.Device);
         }
         }
 
 
         private void device_DeviceRemoved(object sender, DeviceEventArgs e)
         private void device_DeviceRemoved(object sender, DeviceEventArgs e)
         {
         {
-            var task = SendByeByeNotifications(e.Device, false);
+            var task = SendByeByeNotifications(e.Device, false, CancellationToken.None);
             Task.WaitAll(task);
             Task.WaitAll(task);
             DisconnectFromDeviceEvents(e.Device);
             DisconnectFromDeviceEvents(e.Device);
         }
         }
@@ -677,7 +678,7 @@ namespace Rssdp.Infrastructure
                 //else if (!e.Message.Headers.Contains("MAN"))
                 //else if (!e.Message.Headers.Contains("MAN"))
                 //	WriteTrace("Ignoring search request - missing MAN header.");
                 //	WriteTrace("Ignoring search request - missing MAN header.");
                 //else
                 //else
-                ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress);
+                ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress, CancellationToken.None);
             }
             }
         }
         }