EventManager.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Common.Extensions;
  9. using MediaBrowser.Common.Net;
  10. using Microsoft.Extensions.Logging;
  11. namespace Emby.Dlna.Eventing
  12. {
  13. public class EventManager : IEventManager
  14. {
  15. private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
  16. new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
  17. private readonly ILogger _logger;
  18. private readonly IHttpClient _httpClient;
  19. public EventManager(ILogger logger, IHttpClient httpClient)
  20. {
  21. _httpClient = httpClient;
  22. _logger = logger;
  23. }
  24. public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
  25. {
  26. var subscription = GetSubscription(subscriptionId, false);
  27. int timeoutSeconds;
  28. // Remove logging for now because some devices are sending this very frequently
  29. // TODO re-enable with dlna debug logging setting
  30. //_logger.LogDebug("Renewing event subscription for {0} with timeout of {1} to {2}",
  31. // subscription.NotificationType,
  32. // timeout,
  33. // subscription.CallbackUrl);
  34. if (subscription != null)
  35. {
  36. subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
  37. timeoutSeconds = subscription.TimeoutSeconds;
  38. subscription.SubscriptionTime = DateTime.UtcNow;
  39. }
  40. else
  41. {
  42. timeoutSeconds = 300;
  43. }
  44. return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
  45. }
  46. public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
  47. {
  48. var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
  49. var id = "uuid:" + Guid.NewGuid().ToString("N");
  50. // Remove logging for now because some devices are sending this very frequently
  51. // TODO re-enable with dlna debug logging setting
  52. //_logger.LogDebug("Creating event subscription for {0} with timeout of {1} to {2}",
  53. // notificationType,
  54. // timeout,
  55. // callbackUrl);
  56. _subscriptions.TryAdd(id, new EventSubscription
  57. {
  58. Id = id,
  59. CallbackUrl = callbackUrl,
  60. SubscriptionTime = DateTime.UtcNow,
  61. TimeoutSeconds = timeout
  62. });
  63. return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout);
  64. }
  65. private int? ParseTimeout(string header)
  66. {
  67. if (!string.IsNullOrEmpty(header))
  68. {
  69. // Starts with SECOND-
  70. header = header.Split('-').Last();
  71. if (int.TryParse(header, NumberStyles.Integer, _usCulture, out var val))
  72. {
  73. return val;
  74. }
  75. }
  76. return null;
  77. }
  78. public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
  79. {
  80. _logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
  81. _subscriptions.TryRemove(subscriptionId, out EventSubscription sub);
  82. return new EventSubscriptionResponse
  83. {
  84. Content = string.Empty,
  85. ContentType = "text/plain"
  86. };
  87. }
  88. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  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. builder.Append("<" + key + ">");
  129. builder.Append(stateVariables[key]);
  130. builder.Append("</" + key + ">");
  131. builder.Append("</e:property>");
  132. }
  133. builder.Append("</e:propertyset>");
  134. var options = new HttpRequestOptions
  135. {
  136. RequestContent = builder.ToString(),
  137. RequestContentType = "text/xml",
  138. Url = subscription.CallbackUrl,
  139. BufferContent = false
  140. };
  141. options.RequestHeaders.Add("NT", subscription.NotificationType);
  142. options.RequestHeaders.Add("NTS", "upnp:propchange");
  143. options.RequestHeaders.Add("SID", subscription.Id);
  144. options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture));
  145. try
  146. {
  147. using (await _httpClient.SendAsync(options, "NOTIFY").ConfigureAwait(false))
  148. {
  149. }
  150. }
  151. catch (OperationCanceledException)
  152. {
  153. }
  154. catch
  155. {
  156. // Already logged at lower levels
  157. }
  158. finally
  159. {
  160. subscription.IncrementTriggerCount();
  161. }
  162. }
  163. }
  164. }