jellyfin/Emby.Dlna/Eventing/DlnaEventManager.cs

197 lines
7.0 KiB
C#
Raw Normal View History

#pragma warning disable CS1591
using System;
2016-10-29 15:22:20 -07:00
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Mime;
2016-10-29 15:22:20 -07:00
using System.Text;
using System.Threading.Tasks;
2019-01-13 12:16:19 -07:00
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
2016-10-29 15:22:20 -07:00
2016-10-29 15:34:54 -07:00
namespace Emby.Dlna.Eventing
2016-10-29 15:22:20 -07:00
{
public class DlnaEventManager : IDlnaEventManager
2016-10-29 15:22:20 -07:00
{
private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
2016-10-29 15:22:20 -07:00
2020-08-20 08:01:04 -07:00
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory)
2016-10-29 15:22:20 -07:00
{
_httpClientFactory = httpClientFactory;
2016-10-29 15:22:20 -07:00
_logger = logger;
}
2017-10-04 11:51:26 -07:00
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
2016-10-29 15:22:20 -07:00
{
2017-10-04 11:51:26 -07:00
var subscription = GetSubscription(subscriptionId, false);
if (subscription != null)
{
subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
int timeoutSeconds = subscription.TimeoutSeconds;
subscription.SubscriptionTime = DateTime.UtcNow;
2017-10-04 11:51:26 -07:00
_logger.LogDebug(
"Renewing event subscription for {0} with timeout of {1} to {2}",
subscription.NotificationType,
timeoutSeconds,
subscription.CallbackUrl);
2016-10-29 15:22:20 -07:00
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
}
2017-10-04 11:51:26 -07:00
return new EventSubscriptionResponse
{
Content = string.Empty,
ContentType = "text/plain"
};
2016-10-29 15:22:20 -07:00
}
2017-07-20 13:37:13 -07:00
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
2016-10-29 15:22:20 -07:00
{
2017-07-20 13:37:13 -07:00
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
2016-10-29 15:22:20 -07:00
2020-08-20 08:01:04 -07:00
_logger.LogDebug(
"Creating event subscription for {0} with timeout of {1} to {2}",
2019-12-30 08:03:20 -07:00
notificationType,
timeout,
callbackUrl);
2016-10-29 15:22:20 -07:00
_subscriptions.TryAdd(id, new EventSubscription
{
Id = id,
CallbackUrl = callbackUrl,
SubscriptionTime = DateTime.UtcNow,
TimeoutSeconds = timeout
});
2017-07-20 13:37:13 -07:00
return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout);
}
private int? ParseTimeout(string header)
{
if (!string.IsNullOrEmpty(header))
{
// Starts with SECOND-
header = header.Split('-')[^1];
2017-07-20 13:37:13 -07:00
if (int.TryParse(header, NumberStyles.Integer, _usCulture, out var val))
2017-07-20 13:37:13 -07:00
{
return val;
}
}
return null;
2016-10-29 15:22:20 -07:00
}
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
{
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
2016-10-29 15:22:20 -07:00
2020-08-20 08:01:04 -07:00
_subscriptions.TryRemove(subscriptionId, out _);
2016-10-29 15:22:20 -07:00
return new EventSubscriptionResponse
{
2017-07-20 13:37:13 -07:00
Content = string.Empty,
2016-10-29 15:22:20 -07:00
ContentType = "text/plain"
};
}
2017-07-20 13:37:13 -07:00
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
2016-10-29 15:22:20 -07:00
{
var response = new EventSubscriptionResponse
{
2017-07-20 13:37:13 -07:00
Content = string.Empty,
2016-10-29 15:22:20 -07:00
ContentType = "text/plain"
};
response.Headers["SID"] = subscriptionId;
2018-09-12 10:26:21 -07:00
response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString;
2016-10-29 15:22:20 -07:00
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)
2016-10-29 15:22:20 -07:00
{
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)
{
2020-07-22 05:34:51 -07:00
builder.Append("<e:property>")
.Append('<')
.Append(key)
.Append('>')
.Append(stateVariables[key])
.Append("</")
.Append(key)
.Append('>')
.Append("</e:property>");
2016-10-29 15:22:20 -07:00
}
2020-06-15 14:43:52 -07:00
2016-10-29 15:22:20 -07:00
builder.Append("</e:propertyset>");
2020-11-06 08:15:30 -07:00
using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml);
options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType);
options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange");
options.Headers.TryAddWithoutValidation("SID", subscription.Id);
options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture));
2016-10-29 15:22:20 -07:00
try
{
2020-08-31 11:45:23 -07:00
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
2016-10-29 15:22:20 -07:00
}
catch (OperationCanceledException)
{
}
catch
{
// Already logged at lower levels
}
finally
{
subscription.IncrementTriggerCount();
}
}
}
}