diff options
Diffstat (limited to 'Emby.Dlna/Eventing/DlnaEventManager.cs')
| -rw-r--r-- | Emby.Dlna/Eventing/DlnaEventManager.cs | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs new file mode 100644 index 000000000..b66e966df --- /dev/null +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -0,0 +1,202 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using Microsoft.Extensions.Logging; + +namespace Emby.Dlna.Eventing +{ + public class DlnaEventManager : IDlnaEventManager + { + private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions = + new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase); + + private readonly ILogger _logger; + private readonly IHttpClient _httpClient; + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public DlnaEventManager(ILogger logger, IHttpClient httpClient) + { + _httpClient = httpClient; + _logger = logger; + } + + public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl) + { + var subscription = GetSubscription(subscriptionId, false); + if (subscription != null) + { + subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300; + int timeoutSeconds = subscription.TimeoutSeconds; + subscription.SubscriptionTime = DateTime.UtcNow; + + _logger.LogDebug( + "Renewing event subscription for {0} with timeout of {1} to {2}", + subscription.NotificationType, + timeoutSeconds, + subscription.CallbackUrl); + + return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds); + } + + return new EventSubscriptionResponse + { + Content = string.Empty, + ContentType = "text/plain" + }; + } + + public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) + { + var timeout = ParseTimeout(requestedTimeoutString) ?? 300; + var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + + _logger.LogDebug( + "Creating event subscription for {0} with timeout of {1} to {2}", + notificationType, + timeout, + callbackUrl); + + _subscriptions.TryAdd(id, new EventSubscription + { + Id = id, + CallbackUrl = callbackUrl, + SubscriptionTime = DateTime.UtcNow, + TimeoutSeconds = timeout + }); + + return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout); + } + + private int? ParseTimeout(string header) + { + if (!string.IsNullOrEmpty(header)) + { + // Starts with SECOND- + header = header.Split('-').Last(); + + if (int.TryParse(header, NumberStyles.Integer, _usCulture, out var val)) + { + return val; + } + } + + return null; + } + + public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) + { + _logger.LogDebug("Cancelling event subscription {0}", subscriptionId); + + _subscriptions.TryRemove(subscriptionId, out _); + + return new EventSubscriptionResponse + { + Content = string.Empty, + ContentType = "text/plain" + }; + } + + private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds) + { + var response = new EventSubscriptionResponse + { + Content = string.Empty, + ContentType = "text/plain" + }; + + response.Headers["SID"] = subscriptionId; + response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString; + + return response; + } + + public EventSubscription GetSubscription(string id) + { + return GetSubscription(id, false); + } + + private EventSubscription GetSubscription(string id, bool throwOnMissing) + { + if (!_subscriptions.TryGetValue(id, out EventSubscription e) && throwOnMissing) + { + throw new ResourceNotFoundException("Event with Id " + id + " not found."); + } + + return e; + } + + public Task TriggerEvent(string notificationType, IDictionary<string, string> stateVariables) + { + var subs = _subscriptions.Values + .Where(i => !i.IsExpired && string.Equals(notificationType, i.NotificationType, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + var tasks = subs.Select(i => TriggerEvent(i, stateVariables)); + + return Task.WhenAll(tasks); + } + + private async Task TriggerEvent(EventSubscription subscription, IDictionary<string, string> stateVariables) + { + var builder = new StringBuilder(); + + builder.Append("<?xml version=\"1.0\"?>"); + builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">"); + foreach (var key in stateVariables.Keys) + { + builder.Append("<e:property>") + .Append('<') + .Append(key) + .Append('>') + .Append(stateVariables[key]) + .Append("</") + .Append(key) + .Append('>') + .Append("</e:property>"); + } + + builder.Append("</e:propertyset>"); + + var options = new HttpRequestOptions + { + RequestContent = builder.ToString(), + RequestContentType = "text/xml", + Url = subscription.CallbackUrl, + BufferContent = false + }; + + options.RequestHeaders.Add("NT", subscription.NotificationType); + options.RequestHeaders.Add("NTS", "upnp:propchange"); + options.RequestHeaders.Add("SID", subscription.Id); + options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture)); + + try + { + using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false)) + { + } + } + catch (OperationCanceledException) + { + } + catch + { + // Already logged at lower levels + } + finally + { + subscription.IncrementTriggerCount(); + } + } + } +} |
