diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-04-21 12:02:30 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-04-21 12:02:30 -0400 |
| commit | 4331700747440cfa2c2df0cea1abcace9ed60019 (patch) | |
| tree | 59b30193c93c4f1f271f74105798190d8148e71a | |
| parent | 3b4be92038bb517385696d6f12f08c23a79eda44 (diff) | |
support sending upnp events
22 files changed, 528 insertions, 152 deletions
diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs index 3ae9ddc63..b357409c3 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs @@ -51,10 +51,14 @@ namespace MediaBrowser.Api.Dlna public class DlnaServerService : BaseApiService { private readonly IDlnaManager _dlnaManager; + private readonly IContentDirectory _contentDirectory; + private readonly IEventManager _eventManager; - public DlnaServerService(IDlnaManager dlnaManager) + public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IEventManager eventManager) { _dlnaManager = dlnaManager; + _contentDirectory = contentDirectory; + _eventManager = eventManager; } public object Get(GetDescriptionXml request) @@ -66,7 +70,7 @@ namespace MediaBrowser.Api.Dlna public object Get(GetContentDirectory request) { - var xml = _dlnaManager.GetContentDirectoryXml(GetRequestHeaders()); + var xml = _contentDirectory.GetContentDirectoryXml(GetRequestHeaders()); return ResultFactory.GetResult(xml, "text/xml"); } @@ -85,7 +89,7 @@ namespace MediaBrowser.Api.Dlna using (var reader = new StreamReader(request.RequestStream)) { - return _dlnaManager.ProcessControlRequest(new ControlRequest + return _contentDirectory.ProcessControlRequest(new ControlRequest { Headers = GetRequestHeaders(), InputXml = await reader.ReadToEndAsync().ConfigureAwait(false), @@ -128,49 +132,24 @@ namespace MediaBrowser.Api.Dlna var callback = GetHeader("CALLBACK"); var timeoutString = GetHeader("TIMEOUT"); - var timeout = ParseTimeout(timeoutString) ?? 300; + var timeout = ParseTimeout(timeoutString); if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrEmpty(notificationType)) { - RenewEvent(subscriptionId, timeout); - } - else - { - SubscribeToEvent(notificationType, timeout, callback); + return GetSubscriptionResponse(_eventManager.RenewEventSubscription(subscriptionId, timeout)); } - return GetSubscriptionResponse(request.UuId, timeout); + return GetSubscriptionResponse(_eventManager.CreateEventSubscription(notificationType, timeout, callback)); } - UnsubscribeFromEvent(subscriptionId); - return ResultFactory.GetResult("", "text/plain"); + return GetSubscriptionResponse(_eventManager.CancelEventSubscription(subscriptionId)); } - private void UnsubscribeFromEvent(string subscriptionId) + private object GetSubscriptionResponse(EventSubscriptionResponse response) { - - } - - private void SubscribeToEvent(string notificationType, int? timeout, string callback) - { - - } - - private void RenewEvent(string subscriptionId, int? timeout) - { - - } - - private object GetSubscriptionResponse(string uuid, int timeout) - { - var headers = new Dictionary<string, string>(); - - headers["SID"] = "uuid:" + uuid; - headers["TIMEOUT"] = "SECOND-" + timeout.ToString(_usCulture); - - return ResultFactory.GetResult("\r\n", "text/plain", headers); + return ResultFactory.GetResult(response.Content, response.ContentType, response.Headers); } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index 96c207b7b..69533ef9d 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -215,7 +215,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// <returns>Task{HttpResponseInfo}.</returns> /// <exception cref="HttpException"> /// </exception> - private async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod) + public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod) { ValidateParams(options); diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index e583c6b26..4eabbc803 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -44,6 +44,14 @@ namespace MediaBrowser.Common.Net Task<Stream> Get(HttpRequestOptions options); /// <summary> + /// Sends the asynchronous. + /// </summary> + /// <param name="options">The options.</param> + /// <param name="httpMethod">The HTTP method.</param> + /// <returns>Task{HttpResponseInfo}.</returns> + Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod); + + /// <summary> /// Performs a POST request /// </summary> /// <param name="url">The URL.</param> diff --git a/MediaBrowser.Controller/Dlna/EventSubscriptionResponse.cs b/MediaBrowser.Controller/Dlna/EventSubscriptionResponse.cs new file mode 100644 index 000000000..8b551c2a7 --- /dev/null +++ b/MediaBrowser.Controller/Dlna/EventSubscriptionResponse.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Dlna +{ + public class EventSubscriptionResponse + { + public string Content { get; set; } + public string ContentType { get; set; } + + public Dictionary<string, string> Headers { get; set; } + + public EventSubscriptionResponse() + { + Headers = new Dictionary<string, string>(); + } + } +} diff --git a/MediaBrowser.Controller/Dlna/IContentDirectory.cs b/MediaBrowser.Controller/Dlna/IContentDirectory.cs new file mode 100644 index 000000000..e48d498df --- /dev/null +++ b/MediaBrowser.Controller/Dlna/IContentDirectory.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Dlna +{ + public interface IContentDirectory + { + /// <summary> + /// Gets the content directory XML. + /// </summary> + /// <param name="headers">The headers.</param> + /// <returns>System.String.</returns> + string GetContentDirectoryXml(IDictionary<string, string> headers); + + /// <summary> + /// Processes the control request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>ControlResponse.</returns> + ControlResponse ProcessControlRequest(ControlRequest request); + } +} diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index e9e2aae54..b7a06b368 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -65,20 +65,6 @@ namespace MediaBrowser.Controller.Dlna string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId); /// <summary> - /// Gets the content directory XML. - /// </summary> - /// <param name="headers">The headers.</param> - /// <returns>System.String.</returns> - string GetContentDirectoryXml(IDictionary<string, string> headers); - - /// <summary> - /// Processes the control request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>ControlResponse.</returns> - ControlResponse ProcessControlRequest(ControlRequest request); - - /// <summary> /// Gets the icon. /// </summary> /// <param name="filename">The filename.</param> diff --git a/MediaBrowser.Controller/Dlna/IEventManager.cs b/MediaBrowser.Controller/Dlna/IEventManager.cs new file mode 100644 index 000000000..4abf623a9 --- /dev/null +++ b/MediaBrowser.Controller/Dlna/IEventManager.cs @@ -0,0 +1,47 @@ +using MediaBrowser.Model.Dlna; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Dlna +{ + public interface IEventManager + { + /// <summary> + /// Cancels the event subscription. + /// </summary> + /// <param name="subscriptionId">The subscription identifier.</param> + EventSubscriptionResponse CancelEventSubscription(string subscriptionId); + + /// <summary> + /// Renews the event subscription. + /// </summary> + /// <param name="subscriptionId">The subscription identifier.</param> + /// <param name="timeoutSeconds">The timeout seconds.</param> + /// <returns>EventSubscriptionResponse.</returns> + EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds); + + /// <summary> + /// Creates the event subscription. + /// </summary> + /// <param name="notificationType">Type of the notification.</param> + /// <param name="timeoutSeconds">The timeout seconds.</param> + /// <param name="callbackUrl">The callback URL.</param> + /// <returns>EventSubscriptionResponse.</returns> + EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl); + + /// <summary> + /// Gets the subscription. + /// </summary> + /// <param name="id">The identifier.</param> + /// <returns>EventSubscription.</returns> + EventSubscription GetSubscription(string id); + + /// <summary> + /// Triggers the event. + /// </summary> + /// <param name="notificationType">Type of the notification.</param> + /// <param name="stateVariables">The state variables.</param> + /// <returns>Task.</returns> + Task TriggerEvent(string notificationType, IDictionary<string,string> stateVariables); + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 235c3c1e5..cc3f3d08b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -80,7 +80,10 @@ <Compile Include="Collections\ICollectionManager.cs" /> <Compile Include="Dlna\ControlRequest.cs" /> <Compile Include="Dlna\DlnaIconResponse.cs" /> + <Compile Include="Dlna\EventSubscriptionResponse.cs" /> + <Compile Include="Dlna\IContentDirectory.cs" /> <Compile Include="Dlna\IDlnaManager.cs" /> + <Compile Include="Dlna\IEventManager.cs" /> <Compile Include="Drawing\IImageProcessor.cs" /> <Compile Include="Drawing\ImageFormat.cs" /> <Compile Include="Drawing\ImageProcessingOptions.cs" /> diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 0a1a53435..963c68d9a 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -1,12 +1,8 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Dlna.Profiles; using MediaBrowser.Dlna.Server; using MediaBrowser.Model.Dlna; @@ -28,28 +24,20 @@ namespace MediaBrowser.Dlna private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; - private readonly IUserManager _userManager; - private readonly ILibraryManager _libraryManager; - private readonly IDtoService _dtoService; - private readonly IImageProcessor _imageProcessor; - private readonly IUserDataManager _userDataManager; - private readonly IServerConfigurationManager _config; - - public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, IServerConfigurationManager config) + + public DlnaManager(IXmlSerializer xmlSerializer, + IFileSystem fileSystem, + IApplicationPaths appPaths, + ILogger logger, + IJsonSerializer jsonSerializer) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; _appPaths = appPaths; _logger = logger; _jsonSerializer = jsonSerializer; - _userManager = userManager; - _libraryManager = libraryManager; - _dtoService = dtoService; - _imageProcessor = imageProcessor; - _userDataManager = userDataManager; - _config = config; - - DumpProfiles(); + + //DumpProfiles(); } public IEnumerable<DeviceProfile> GetProfiles() @@ -499,37 +487,6 @@ namespace MediaBrowser.Dlna return new DescriptionXmlBuilder(profile, serverUuId).GetXml(); } - public string GetContentDirectoryXml(IDictionary<string, string> headers) - { - var profile = GetProfile(headers) ?? - GetDefaultProfile(); - - return new ContentDirectoryXmlBuilder(profile).GetXml(); - } - - public ControlResponse ProcessControlRequest(ControlRequest request) - { - var profile = GetProfile(request.Headers) - ?? GetDefaultProfile(); - - var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId); - - var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase)); - - var user = GetUser(profile); - - return new ControlHandler( - _logger, - _libraryManager, - profile, - serverAddress, - _dtoService, - _imageProcessor, - _userDataManager, - user) - .ProcessControlRequest(request); - } - public DlnaIconResponse GetIcon(string filename) { var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase) @@ -542,33 +499,5 @@ namespace MediaBrowser.Dlna Stream = GetType().Assembly.GetManifestResourceStream("MediaBrowser.Dlna.Images." + filename.ToLower()) }; } - - - - private User GetUser(DeviceProfile profile) - { - if (!string.IsNullOrEmpty(profile.UserId)) - { - var user = _userManager.GetUserById(new Guid(profile.UserId)); - - if (user != null) - { - return user; - } - } - - if (!string.IsNullOrEmpty(_config.Configuration.DlnaOptions.DefaultUserId)) - { - var user = _userManager.GetUserById(new Guid(_config.Configuration.DlnaOptions.DefaultUserId)); - - if (user != null) - { - return user; - } - } - - // No configuration so it's going to be pretty arbitrary - return _userManager.Users.First(); - } } }
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Eventing/EventManager.cs b/MediaBrowser.Dlna/Eventing/EventManager.cs new file mode 100644 index 000000000..3961c366a --- /dev/null +++ b/MediaBrowser.Dlna/Eventing/EventManager.cs @@ -0,0 +1,171 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Dlna.Eventing +{ + public class EventManager : IEventManager + { + private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions = + new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase); + + private readonly ILogger _logger; + private readonly IHttpClient _httpClient; + + public EventManager(ILogManager logManager, IHttpClient httpClient) + { + _httpClient = httpClient; + _logger = logManager.GetLogger("DlnaEventManager"); + } + + public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds) + { + var timeout = timeoutSeconds ?? 300; + + var subscription = GetSubscription(subscriptionId, true); + + _logger.Debug("Renewing event subscription for {0} with timeout of {1} to {2}", + subscription.NotificationType, + timeout, + subscription.CallbackUrl); + + subscription.TimeoutSeconds = timeout; + subscription.SubscriptionTime = DateTime.UtcNow; + + return GetEventSubscriptionResponse(subscriptionId, timeout); + } + + public EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl) + { + var timeout = timeoutSeconds ?? 300; + var id = Guid.NewGuid().ToString("N"); + + _logger.Debug("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, timeout); + } + + public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) + { + _logger.Debug("Cancelling event subscription {0}", subscriptionId); + + EventSubscription sub; + _subscriptions.TryRemove(subscriptionId, out sub); + + return new EventSubscriptionResponse + { + Content = "\r\n", + ContentType = "text/plain" + }; + } + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, int timeoutSeconds) + { + var response = new EventSubscriptionResponse + { + Content = "\r\n", + ContentType = "text/plain" + }; + + response.Headers["SID"] = "uuid:" + subscriptionId; + response.Headers["TIMEOUT"] = "SECOND-" + timeoutSeconds.ToString(_usCulture); + + return response; + } + + public EventSubscription GetSubscription(string id) + { + return GetSubscription(id, false); + } + + private EventSubscription GetSubscription(string id, bool throwOnMissing) + { + EventSubscription e; + + if (!_subscriptions.TryGetValue(id, out 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>"); + builder.Append("<" + key + ">"); + builder.Append(stateVariables[key]); + builder.Append("</" + key + ">"); + builder.Append("</e:property>"); + } + builder.Append("</e:propertyset>"); + + var options = new HttpRequestOptions + { + RequestContent = builder.ToString(), + RequestContentType = "text/xml", + Url = subscription.CallbackUrl + }; + + options.RequestHeaders.Add("NT", subscription.NotificationType); + options.RequestHeaders.Add("NTS", "upnp:propchange"); + options.RequestHeaders.Add("SID", "uuid:" + subscription.Id); + options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture)); + + try + { + await _httpClient.SendAsync(options, "NOTIFY").ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch + { + // Already logged at lower levels + } + finally + { + subscription.IncrementTriggerCount(); + } + } + } +} diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index 016cac4d6..81ddf7283 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -53,6 +53,7 @@ </Compile> <Compile Include="DlnaManager.cs" /> <Compile Include="Common\Argument.cs" /> + <Compile Include="Eventing\EventManager.cs" /> <Compile Include="PlayTo\CurrentIdEventArgs.cs" /> <Compile Include="PlayTo\Device.cs"> <SubType>Code</SubType> @@ -73,6 +74,7 @@ <Compile Include="Profiles\Windows81Profile.cs" /> <Compile Include="Profiles\WindowsMediaCenterProfile.cs" /> <Compile Include="Profiles\WindowsPhoneProfile.cs" /> + <Compile Include="Server\ContentDirectory.cs" /> <Compile Include="Server\ControlHandler.cs" /> <Compile Include="Server\ServiceActionListBuilder.cs" /> <Compile Include="Server\ContentDirectoryXmlBuilder.cs" /> diff --git a/MediaBrowser.Dlna/Profiles/Windows81Profile.cs b/MediaBrowser.Dlna/Profiles/Windows81Profile.cs index 000e07b0a..5e74884a2 100644 --- a/MediaBrowser.Dlna/Profiles/Windows81Profile.cs +++ b/MediaBrowser.Dlna/Profiles/Windows81Profile.cs @@ -25,6 +25,15 @@ namespace MediaBrowser.Dlna.Profiles }, new TranscodingProfile { + Protocol = "hls", + Container = "ts", + VideoCodec = "h264", + AudioCodec = "aac", + Type = DlnaProfileType.Video, + VideoProfile = "Baseline" + }, + new TranscodingProfile + { Container = "ts", VideoCodec = "h264", AudioCodec = "aac", diff --git a/MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs b/MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs index 6ed6abc76..80cc8ad71 100644 --- a/MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs +++ b/MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs @@ -20,6 +20,15 @@ namespace MediaBrowser.Dlna.Profiles }, new TranscodingProfile { + Protocol = "hls", + Container = "ts", + VideoCodec = "h264", + AudioCodec = "aac", + Type = DlnaProfileType.Video, + VideoProfile = "Baseline" + }, + new TranscodingProfile + { Container = "mp4", VideoCodec = "h264", AudioCodec = "aac", diff --git a/MediaBrowser.Dlna/Server/ContentDirectory.cs b/MediaBrowser.Dlna/Server/ContentDirectory.cs new file mode 100644 index 000000000..c5b336090 --- /dev/null +++ b/MediaBrowser.Dlna/Server/ContentDirectory.cs @@ -0,0 +1,151 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; + +namespace MediaBrowser.Dlna.Server +{ + public class ContentDirectory : IContentDirectory, IDisposable + { + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; + private readonly IUserDataManager _userDataManager; + private readonly IDlnaManager _dlna; + private readonly IServerConfigurationManager _config; + private readonly IUserManager _userManager; + + private readonly IEventManager _eventManager; + + private int _systemUpdateId; + private Timer _systemUpdateTimer; + + public ContentDirectory(IDlnaManager dlna, + IUserDataManager userDataManager, + IImageProcessor imageProcessor, + IDtoService dtoService, + ILibraryManager libraryManager, + ILogManager logManager, + IServerConfigurationManager config, + IUserManager userManager, + IEventManager eventManager) + { + _dlna = dlna; + _userDataManager = userDataManager; + _imageProcessor = imageProcessor; + _dtoService = dtoService; + _libraryManager = libraryManager; + _config = config; + _userManager = userManager; + _eventManager = eventManager; + _logger = logManager.GetLogger("DlnaContentDirectory"); + + _systemUpdateTimer = new Timer(SystemUdpateTimerCallback, null, Timeout.Infinite, + Convert.ToInt64(TimeSpan.FromMinutes(60).TotalMilliseconds)); + } + + public string GetContentDirectoryXml(IDictionary<string, string> headers) + { + var profile = _dlna.GetProfile(headers) ?? + _dlna.GetDefaultProfile(); + + return new ContentDirectoryXmlBuilder(profile).GetXml(); + } + + public ControlResponse ProcessControlRequest(ControlRequest request) + { + var profile = _dlna.GetProfile(request.Headers) ?? + _dlna.GetDefaultProfile(); + + var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId); + + var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase)); + + var user = GetUser(profile); + + return new ControlHandler( + _logger, + _libraryManager, + profile, + serverAddress, + _dtoService, + _imageProcessor, + _userDataManager, + user, + _systemUpdateId) + .ProcessControlRequest(request); + } + + private User GetUser(DeviceProfile profile) + { + if (!string.IsNullOrEmpty(profile.UserId)) + { + var user = _userManager.GetUserById(new Guid(profile.UserId)); + + if (user != null) + { + return user; + } + } + + if (!string.IsNullOrEmpty(_config.Configuration.DlnaOptions.DefaultUserId)) + { + var user = _userManager.GetUserById(new Guid(_config.Configuration.DlnaOptions.DefaultUserId)); + + if (user != null) + { + return user; + } + } + + // No configuration so it's going to be pretty arbitrary + return _userManager.Users.First(); + } + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private async void SystemUdpateTimerCallback(object state) + { + var values = new Dictionary<string, string>(); + + _systemUpdateId++; + values["SystemUpdateID"] = _systemUpdateId.ToString(_usCulture); + + try + { + await _eventManager.TriggerEvent("upnp:event", values).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending system update notification", ex); + } + } + + private readonly object _disposeLock = new object(); + public void Dispose() + { + lock (_disposeLock) + { + DisposeUpdateTimer(); + } + } + + private void DisposeUpdateTimer() + { + if (_systemUpdateTimer != null) + { + _systemUpdateTimer.Dispose(); + _systemUpdateTimer = null; + } + } + } +} diff --git a/MediaBrowser.Dlna/Server/ControlHandler.cs b/MediaBrowser.Dlna/Server/ControlHandler.cs index f97fdc88e..88c0349db 100644 --- a/MediaBrowser.Dlna/Server/ControlHandler.cs +++ b/MediaBrowser.Dlna/Server/ControlHandler.cs @@ -42,10 +42,10 @@ namespace MediaBrowser.Dlna.Server private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"; private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; - private int systemID = 0; + private readonly int _systemUpdateId; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user) + public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId) { _logger = logger; _libraryManager = libraryManager; @@ -55,6 +55,7 @@ namespace MediaBrowser.Dlna.Server _imageProcessor = imageProcessor; _userDataManager = userDataManager; _user = user; + _systemUpdateId = systemUpdateId; } public ControlResponse ProcessControlRequest(ControlRequest request) @@ -205,7 +206,7 @@ namespace MediaBrowser.Dlna.Server private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID() { - return new Headers { { "Id", systemID.ToString(_usCulture) } }; + return new Headers { { "Id", _systemUpdateId.ToString(_usCulture) } }; } private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList() @@ -308,7 +309,7 @@ namespace MediaBrowser.Dlna.Server new KeyValuePair<string,string>("Result", resXML), new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)), new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)), - new KeyValuePair<string,string>("UpdateID", systemID.ToString(_usCulture)) + new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture)) }; } @@ -382,7 +383,7 @@ namespace MediaBrowser.Dlna.Server new KeyValuePair<string,string>("Result", resXML), new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)), new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)), - new KeyValuePair<string,string>("UpdateID", systemID.ToString(_usCulture)) + new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture)) }; } diff --git a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs index 07552cd3e..4b66e489f 100644 --- a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs +++ b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Dlna.Server var builder = new StringBuilder(); builder.Append("<?xml version=\"1.0\"?>"); - builder.Append("<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\" xmlns:sec=\"http://www.sec.co.kr/dlna\">"); + builder.Append("<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">"); builder.Append("<specVersion>"); builder.Append("<major>1</major>"); @@ -59,16 +59,8 @@ namespace MediaBrowser.Dlna.Server { builder.Append("<UDN>uuid:" + SecurityElement.Escape(_serverUdn) + "</UDN>"); builder.Append("<dlna:X_DLNACAP>" + SecurityElement.Escape(_profile.XDlnaCap ?? string.Empty) + "</dlna:X_DLNACAP>"); - - if (!string.IsNullOrWhiteSpace(_profile.XDlnaDoc)) - { - builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + - SecurityElement.Escape(_profile.XDlnaDoc) + "</dlna:X_DLNADOC>"); - } - else - { - builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">DMS-1.50</dlna:X_DLNADOC>"); - } + + builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + SecurityElement.Escape(_profile.XDlnaDoc ?? string.Empty) + "</dlna:X_DLNADOC>"); builder.Append("<friendlyName>" + SecurityElement.Escape(_profile.FriendlyName ?? string.Empty) + "</friendlyName>"); builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>"); @@ -80,9 +72,6 @@ namespace MediaBrowser.Dlna.Server builder.Append("<modelURL>" + SecurityElement.Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>"); builder.Append("<serialNumber>" + SecurityElement.Escape(_profile.SerialNumber ?? string.Empty) + "</serialNumber>"); - builder.Append("<sec:ProductCap>DCM10,getMediaInfo.sec</sec:ProductCap>"); - builder.Append("<sec:X_ProductCap>DCM10,getMediaInfo.sec</sec:X_ProductCap>"); - if (!string.IsNullOrWhiteSpace(_profile.SonyAggregationFlags)) { builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">" + SecurityElement.Escape(_profile.SonyAggregationFlags) + "</av:aggregationFlags>"); diff --git a/MediaBrowser.Dlna/Server/SsdpHandler.cs b/MediaBrowser.Dlna/Server/SsdpHandler.cs index 236078df0..0430f6a02 100644 --- a/MediaBrowser.Dlna/Server/SsdpHandler.cs +++ b/MediaBrowser.Dlna/Server/SsdpHandler.cs @@ -117,7 +117,6 @@ namespace MediaBrowser.Dlna.Server if (_config.Configuration.DlnaOptions.EnableDebugLogging) { _logger.Debug("{0} - Datagram method: {1}", endpoint, method); - //_logger.Debug(headers); } if (string.Equals(method, "M-SEARCH", StringComparison.OrdinalIgnoreCase)) @@ -234,7 +233,10 @@ namespace MediaBrowser.Dlna.Server private void NotifyAll() { - _logger.Debug("Sending alive notifications"); + if (_config.Configuration.DlnaOptions.EnableDebugLogging) + { + _logger.Debug("Sending alive notifications"); + } foreach (var d in Devices) { NotifyDevice(d, "alive", false); @@ -243,7 +245,6 @@ namespace MediaBrowser.Dlna.Server private void NotifyDevice(UpnpDevice dev, string type, bool sticky) { - _logger.Debug("NotifyDevice"); var builder = new StringBuilder(); const string argFormat = "{0}: {1}\r\n"; @@ -258,7 +259,11 @@ namespace MediaBrowser.Dlna.Server builder.AppendFormat(argFormat, "USN", dev.USN); builder.Append("\r\n"); - _logger.Debug("{0} said {1}", dev.USN, type); + if (_config.Configuration.DlnaOptions.EnableDebugLogging) + { + _logger.Debug("{0} said {1}", dev.USN, type); + } + SendDatagram(_ssdpEndp, dev.Address, builder.ToString(), sticky); } diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 0df8f2b7f..fe2fe2091 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -122,6 +122,9 @@ <Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs"> <Link>Dlna\DlnaMaps.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Dlna\EventSubscription.cs"> + <Link>Dlna\EventSubscription.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Dlna\Filter.cs"> <Link>Dlna\Filter.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 570ddaa31..b833a19a7 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -109,6 +109,9 @@ <Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs"> <Link>Dlna\DlnaMaps.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Dlna\EventSubscription.cs"> + <Link>Dlna\EventSubscription.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Dlna\Filter.cs"> <Link>Dlna\Filter.cs</Link> </Compile> diff --git a/MediaBrowser.Model/Dlna/EventSubscription.cs b/MediaBrowser.Model/Dlna/EventSubscription.cs new file mode 100644 index 000000000..863ea508a --- /dev/null +++ b/MediaBrowser.Model/Dlna/EventSubscription.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.Dlna +{ + public class EventSubscription + { + public string Id { get; set; } + public string CallbackUrl { get; set; } + public string NotificationType { get; set; } + + public DateTime SubscriptionTime { get; set; } + public int TimeoutSeconds { get; set; } + + public long TriggerCount { get; set; } + + public void IncrementTriggerCount() + { + if (TriggerCount == long.MaxValue) + { + TriggerCount = 0; + } + + TriggerCount++; + } + + public bool IsExpired + { + get + { + return SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow; + } + } + } +} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index a4f0a609f..c18d7e8dc 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -73,6 +73,7 @@ <Compile Include="Dlna\DeviceProfileInfo.cs" /> <Compile Include="Dlna\DirectPlayProfile.cs" /> <Compile Include="Dlna\DlnaMaps.cs" /> + <Compile Include="Dlna\EventSubscription.cs" /> <Compile Include="Dlna\Filter.cs" /> <Compile Include="Dlna\MediaFormatProfile.cs" /> <Compile Include="Dlna\MediaFormatProfileResolver.cs" /> diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 30aeaf877..8ee060c3c 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -32,7 +32,9 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Themes; using MediaBrowser.Dlna; +using MediaBrowser.Dlna.Eventing; using MediaBrowser.Dlna.PlayTo; +using MediaBrowser.Dlna.Server; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.Model.Logging; @@ -506,9 +508,15 @@ namespace MediaBrowser.ServerApplication var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger); RegisterSingleInstance<IAppThemeManager>(appThemeManager); - var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer, UserManager, LibraryManager, DtoService, ImageProcessor, UserDataManager, ServerConfigurationManager); + var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("Dlna"), JsonSerializer); RegisterSingleInstance<IDlnaManager>(dlnaManager); + var dlnaEventManager = new EventManager(LogManager, HttpClient); + RegisterSingleInstance<IEventManager>(dlnaEventManager); + + var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, DtoService, LibraryManager, LogManager, ServerConfigurationManager, UserManager, dlnaEventManager); + RegisterSingleInstance<IContentDirectory>(contentDirectory); + var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); RegisterSingleInstance<ICollectionManager>(collectionManager); |
