DiscoveredSsdpDevice.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net.Http;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Net.Http.Headers;
  8. namespace Rssdp
  9. {
  10. /// <summary>
  11. /// Represents a discovered device, containing basic information about the device and the location of it's full device description document. Also provides convenience methods for retrieving the device description document.
  12. /// </summary>
  13. /// <seealso cref="SsdpDevice"/>
  14. /// <seealso cref="Rssdp.Infrastructure.ISsdpDeviceLocator"/>
  15. public sealed class DiscoveredSsdpDevice
  16. {
  17. #region Fields
  18. private SsdpRootDevice _Device;
  19. private DateTimeOffset _AsAt;
  20. private static HttpClient s_DefaultHttpClient;
  21. #endregion
  22. #region Public Properties
  23. /// <summary>
  24. /// Sets or returns the type of notification, being either a uuid, device type, service type or upnp:rootdevice.
  25. /// </summary>
  26. public string NotificationType { get; set; }
  27. /// <summary>
  28. /// Sets or returns the universal service name (USN) of the device.
  29. /// </summary>
  30. public string Usn { get; set; }
  31. /// <summary>
  32. /// Sets or returns a URL pointing to the device description document for this device.
  33. /// </summary>
  34. public Uri DescriptionLocation { get; set; }
  35. /// <summary>
  36. /// Sets or returns the length of time this information is valid for (from the <see cref="AsAt"/> time).
  37. /// </summary>
  38. public TimeSpan CacheLifetime { get; set; }
  39. /// <summary>
  40. /// Sets or returns the date and time this information was received.
  41. /// </summary>
  42. public DateTimeOffset AsAt
  43. {
  44. get { return _AsAt; }
  45. set
  46. {
  47. if (_AsAt != value)
  48. {
  49. _AsAt = value;
  50. _Device = null;
  51. }
  52. }
  53. }
  54. /// <summary>
  55. /// Returns the headers from the SSDP device response message
  56. /// </summary>
  57. public HttpHeaders ResponseHeaders { get; set; }
  58. #endregion
  59. #region Public Methods
  60. /// <summary>
  61. /// Returns true if this device information has expired, based on the current date/time, and the <see cref="CacheLifetime"/> &amp; <see cref="AsAt"/> properties.
  62. /// </summary>
  63. /// <returns></returns>
  64. public bool IsExpired()
  65. {
  66. return this.CacheLifetime == TimeSpan.Zero || this.AsAt.Add(this.CacheLifetime) <= DateTimeOffset.Now;
  67. }
  68. /// <summary>
  69. /// Retrieves the device description document specified by the <see cref="DescriptionLocation"/> property.
  70. /// </summary>
  71. /// <remarks>
  72. /// <para>This method may choose to cache (or return cached) information if called multiple times within the <see cref="CacheLifetime"/> period.</para>
  73. /// </remarks>
  74. /// <returns>An <see cref="SsdpDevice"/> instance describing the full device details.</returns>
  75. public async Task<SsdpDevice> GetDeviceInfo()
  76. {
  77. var device = _Device;
  78. if (device == null || this.IsExpired())
  79. return await GetDeviceInfo(GetDefaultClient());
  80. else
  81. return device;
  82. }
  83. /// <summary>
  84. /// Retrieves the device description document specified by the <see cref="DescriptionLocation"/> property using the provided <see cref="System.Net.Http.HttpClient"/> instance.
  85. /// </summary>
  86. /// <remarks>
  87. /// <para>This method may choose to cache (or return cached) information if called multiple times within the <see cref="CacheLifetime"/> period.</para>
  88. /// <para>This method performs no error handling, if an exception occurs downloading or parsing the document it will be thrown to the calling code. Ensure you setup correct error handling for these scenarios.</para>
  89. /// </remarks>
  90. /// <param name="downloadHttpClient">A <see cref="System.Net.Http.HttpClient"/> to use when downloading the document data.</param>
  91. /// <returns>An <see cref="SsdpDevice"/> instance describing the full device details.</returns>
  92. public async Task<SsdpRootDevice> GetDeviceInfo(HttpClient downloadHttpClient)
  93. {
  94. if (_Device == null || this.IsExpired())
  95. {
  96. var rawDescriptionDocument = await downloadHttpClient.GetAsync(this.DescriptionLocation);
  97. rawDescriptionDocument.EnsureSuccessStatusCode();
  98. // Not using ReadAsStringAsync() here as some devices return the content type as utf-8 not UTF-8,
  99. // which causes an (unneccesary) exception.
  100. var data = await rawDescriptionDocument.Content.ReadAsByteArrayAsync();
  101. _Device = new SsdpRootDevice(this.DescriptionLocation, this.CacheLifetime, System.Text.UTF8Encoding.UTF8.GetString(data, 0, data.Length));
  102. }
  103. return _Device;
  104. }
  105. #endregion
  106. #region Overrides
  107. /// <summary>
  108. /// Returns the device's <see cref="Usn"/> value.
  109. /// </summary>
  110. /// <returns>A string containing the device's universal service name.</returns>
  111. public override string ToString()
  112. {
  113. return this.Usn;
  114. }
  115. #endregion
  116. #region Private Methods
  117. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Can't call dispose on the handler since we pass it to the HttpClient, which outlives the scope of this method.")]
  118. private static HttpClient GetDefaultClient()
  119. {
  120. if (s_DefaultHttpClient == null)
  121. {
  122. var handler = new System.Net.Http.HttpClientHandler();
  123. try
  124. {
  125. if (handler.SupportsAutomaticDecompression)
  126. handler.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip;
  127. s_DefaultHttpClient = new HttpClient(handler);
  128. }
  129. catch
  130. {
  131. if (handler != null)
  132. handler.Dispose();
  133. throw;
  134. }
  135. }
  136. return s_DefaultHttpClient;
  137. }
  138. #endregion
  139. }
  140. }