DlnaEventManager.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Net.Http;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using MediaBrowser.Common.Extensions;
  11. using MediaBrowser.Common.Net;
  12. using Microsoft.Extensions.Logging;
  13. namespace Emby.Dlna.Eventing
  14. {
  15. public class DlnaEventManager : IDlnaEventManager
  16. {
  17. private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
  18. new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
  19. private readonly ILogger _logger;
  20. private readonly IHttpClient _httpClient;
  21. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  22. public DlnaEventManager(ILogger logger, IHttpClient httpClient)
  23. {
  24. _httpClient = httpClient;
  25. _logger = logger;
  26. }
  27. public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
  28. {
  29. var subscription = GetSubscription(subscriptionId, false);
  30. if (subscription != null)
  31. {
  32. subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
  33. int timeoutSeconds = subscription.TimeoutSeconds;
  34. subscription.SubscriptionTime = DateTime.UtcNow;
  35. _logger.LogDebug(
  36. "Renewing event subscription for {0} with timeout of {1} to {2}",
  37. subscription.NotificationType,
  38. timeoutSeconds,
  39. subscription.CallbackUrl);
  40. return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
  41. }
  42. return new EventSubscriptionResponse
  43. {
  44. Content = string.Empty,
  45. ContentType = "text/plain"
  46. };
  47. }
  48. public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
  49. {
  50. var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
  51. var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
  52. _logger.LogDebug(
  53. "Creating event subscription for {0} with timeout of {1} to {2}",
  54. notificationType,
  55. timeout,
  56. callbackUrl);
  57. _subscriptions.TryAdd(id, new EventSubscription
  58. {
  59. Id = id,
  60. CallbackUrl = callbackUrl,
  61. SubscriptionTime = DateTime.UtcNow,
  62. TimeoutSeconds = timeout
  63. });
  64. return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout);
  65. }
  66. private int? ParseTimeout(string header)
  67. {
  68. if (!string.IsNullOrEmpty(header))
  69. {
  70. // Starts with SECOND-
  71. header = header.Split('-').Last();
  72. if (int.TryParse(header, NumberStyles.Integer, _usCulture, out var val))
  73. {
  74. return val;
  75. }
  76. }
  77. return null;
  78. }
  79. public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
  80. {
  81. _logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
  82. _subscriptions.TryRemove(subscriptionId, out _);
  83. return new EventSubscriptionResponse
  84. {
  85. Content = string.Empty,
  86. ContentType = "text/plain"
  87. };
  88. }
  89. private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
  90. {
  91. var response = new EventSubscriptionResponse
  92. {
  93. Content = string.Empty,
  94. ContentType = "text/plain"
  95. };
  96. response.Headers["SID"] = subscriptionId;
  97. response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString;
  98. return response;
  99. }
  100. public EventSubscription GetSubscription(string id)
  101. {
  102. return GetSubscription(id, false);
  103. }
  104. private EventSubscription GetSubscription(string id, bool throwOnMissing)
  105. {
  106. if (!_subscriptions.TryGetValue(id, out EventSubscription e) && throwOnMissing)
  107. {
  108. throw new ResourceNotFoundException("Event with Id " + id + " not found.");
  109. }
  110. return e;
  111. }
  112. public Task TriggerEvent(string notificationType, IDictionary<string, string> stateVariables)
  113. {
  114. var subs = _subscriptions.Values
  115. .Where(i => !i.IsExpired && string.Equals(notificationType, i.NotificationType, StringComparison.OrdinalIgnoreCase))
  116. .ToList();
  117. var tasks = subs.Select(i => TriggerEvent(i, stateVariables));
  118. return Task.WhenAll(tasks);
  119. }
  120. private async Task TriggerEvent(EventSubscription subscription, IDictionary<string, string> stateVariables)
  121. {
  122. var builder = new StringBuilder();
  123. builder.Append("<?xml version=\"1.0\"?>");
  124. builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
  125. foreach (var key in stateVariables.Keys)
  126. {
  127. builder.Append("<e:property>")
  128. .Append('<')
  129. .Append(key)
  130. .Append('>')
  131. .Append(stateVariables[key])
  132. .Append("</")
  133. .Append(key)
  134. .Append('>')
  135. .Append("</e:property>");
  136. }
  137. builder.Append("</e:propertyset>");
  138. var options = new HttpRequestOptions
  139. {
  140. RequestContent = builder.ToString(),
  141. RequestContentType = "text/xml",
  142. Url = subscription.CallbackUrl,
  143. BufferContent = false
  144. };
  145. options.RequestHeaders.Add("NT", subscription.NotificationType);
  146. options.RequestHeaders.Add("NTS", "upnp:propchange");
  147. options.RequestHeaders.Add("SID", subscription.Id);
  148. options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture));
  149. try
  150. {
  151. using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
  152. {
  153. }
  154. }
  155. catch (OperationCanceledException)
  156. {
  157. }
  158. catch
  159. {
  160. // Already logged at lower levels
  161. }
  162. finally
  163. {
  164. subscription.IncrementTriggerCount();
  165. }
  166. }
  167. }
  168. }