EventManager.cs 6.5 KB

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