jellyfin/Emby.Dlna/Eventing/DlnaEventManager.cs

184 lines
6.8 KiB
C#
Raw Normal View History

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