DiscoveredSsdpDevice.cs 5.4 KB

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