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(); - - 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 /// Task{HttpResponseInfo}. /// /// - private async Task SendAsync(HttpRequestOptions options, string httpMethod) + public async Task 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 @@ -43,6 +43,14 @@ namespace MediaBrowser.Common.Net /// Task{Stream}. Task Get(HttpRequestOptions options); + /// + /// Sends the asynchronous. + /// + /// The options. + /// The HTTP method. + /// Task{HttpResponseInfo}. + Task SendAsync(HttpRequestOptions options, string httpMethod); + /// /// Performs a POST request /// 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 Headers { get; set; } + + public EventSubscriptionResponse() + { + Headers = new Dictionary(); + } + } +} 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 + { + /// + /// Gets the content directory XML. + /// + /// The headers. + /// System.String. + string GetContentDirectoryXml(IDictionary headers); + + /// + /// Processes the control request. + /// + /// The request. + /// ControlResponse. + 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 @@ -64,20 +64,6 @@ namespace MediaBrowser.Controller.Dlna /// System.String. string GetServerDescriptionXml(IDictionary headers, string serverUuId); - /// - /// Gets the content directory XML. - /// - /// The headers. - /// System.String. - string GetContentDirectoryXml(IDictionary headers); - - /// - /// Processes the control request. - /// - /// The request. - /// ControlResponse. - ControlResponse ProcessControlRequest(ControlRequest request); - /// /// Gets the icon. /// 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 + { + /// + /// Cancels the event subscription. + /// + /// The subscription identifier. + EventSubscriptionResponse CancelEventSubscription(string subscriptionId); + + /// + /// Renews the event subscription. + /// + /// The subscription identifier. + /// The timeout seconds. + /// EventSubscriptionResponse. + EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds); + + /// + /// Creates the event subscription. + /// + /// Type of the notification. + /// The timeout seconds. + /// The callback URL. + /// EventSubscriptionResponse. + EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl); + + /// + /// Gets the subscription. + /// + /// The identifier. + /// EventSubscription. + EventSubscription GetSubscription(string id); + + /// + /// Triggers the event. + /// + /// Type of the notification. + /// The state variables. + /// Task. + Task TriggerEvent(string notificationType, IDictionary 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 @@ + + + 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 GetProfiles() @@ -499,37 +487,6 @@ namespace MediaBrowser.Dlna return new DescriptionXmlBuilder(profile, serverUuId).GetXml(); } - public string GetContentDirectoryXml(IDictionary 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 _subscriptions = + new ConcurrentDictionary(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 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 stateVariables) + { + var builder = new StringBuilder(); + + builder.Append(""); + builder.Append(""); + foreach (var key in stateVariables.Keys) + { + builder.Append(""); + builder.Append("<" + key + ">"); + builder.Append(stateVariables[key]); + builder.Append(""); + builder.Append(""); + } + builder.Append(""); + + 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 @@ + Code @@ -73,6 +74,7 @@ + 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 @@ -24,6 +24,15 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Audio }, new TranscodingProfile + { + Protocol = "hls", + Container = "ts", + VideoCodec = "h264", + AudioCodec = "aac", + Type = DlnaProfileType.Video, + VideoProfile = "Baseline" + }, + new TranscodingProfile { Container = "ts", VideoCodec = "h264", 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 @@ -19,6 +19,15 @@ namespace MediaBrowser.Dlna.Profiles Type = DlnaProfileType.Audio }, new TranscodingProfile + { + Protocol = "hls", + Container = "ts", + VideoCodec = "h264", + AudioCodec = "aac", + Type = DlnaProfileType.Video, + VideoProfile = "Baseline" + }, + new TranscodingProfile { Container = "mp4", VideoCodec = "h264", 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 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(); + + _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> HandleGetSystemUpdateID() { - return new Headers { { "Id", systemID.ToString(_usCulture) } }; + return new Headers { { "Id", _systemUpdateId.ToString(_usCulture) } }; } private IEnumerable> HandleXGetFeatureList() @@ -308,7 +309,7 @@ namespace MediaBrowser.Dlna.Server new KeyValuePair("Result", resXML), new KeyValuePair("NumberReturned", provided.ToString(_usCulture)), new KeyValuePair("TotalMatches", totalCount.ToString(_usCulture)), - new KeyValuePair("UpdateID", systemID.ToString(_usCulture)) + new KeyValuePair("UpdateID", _systemUpdateId.ToString(_usCulture)) }; } @@ -382,7 +383,7 @@ namespace MediaBrowser.Dlna.Server new KeyValuePair("Result", resXML), new KeyValuePair("NumberReturned", provided.ToString(_usCulture)), new KeyValuePair("TotalMatches", totalCount.ToString(_usCulture)), - new KeyValuePair("UpdateID", systemID.ToString(_usCulture)) + new KeyValuePair("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(""); - builder.Append(""); + builder.Append(""); builder.Append(""); builder.Append("1"); @@ -59,16 +59,8 @@ namespace MediaBrowser.Dlna.Server { builder.Append("uuid:" + SecurityElement.Escape(_serverUdn) + ""); builder.Append("" + SecurityElement.Escape(_profile.XDlnaCap ?? string.Empty) + ""); - - if (!string.IsNullOrWhiteSpace(_profile.XDlnaDoc)) - { - builder.Append("" + - SecurityElement.Escape(_profile.XDlnaDoc) + ""); - } - else - { - builder.Append("DMS-1.50"); - } + + builder.Append("" + SecurityElement.Escape(_profile.XDlnaDoc ?? string.Empty) + ""); builder.Append("" + SecurityElement.Escape(_profile.FriendlyName ?? string.Empty) + ""); builder.Append("urn:schemas-upnp-org:device:MediaServer:1"); @@ -80,9 +72,6 @@ namespace MediaBrowser.Dlna.Server builder.Append("" + SecurityElement.Escape(_profile.ModelUrl ?? string.Empty) + ""); builder.Append("" + SecurityElement.Escape(_profile.SerialNumber ?? string.Empty) + ""); - builder.Append("DCM10,getMediaInfo.sec"); - builder.Append("DCM10,getMediaInfo.sec"); - if (!string.IsNullOrWhiteSpace(_profile.SonyAggregationFlags)) { builder.Append("" + SecurityElement.Escape(_profile.SonyAggregationFlags) + ""); 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 @@ Dlna\DlnaMaps.cs + + Dlna\EventSubscription.cs + Dlna\Filter.cs 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 @@ Dlna\DlnaMaps.cs + + Dlna\EventSubscription.cs + Dlna\Filter.cs 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 @@ + 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(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(dlnaManager); + var dlnaEventManager = new EventManager(LogManager, HttpClient); + RegisterSingleInstance(dlnaEventManager); + + var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, DtoService, LibraryManager, LogManager, ServerConfigurationManager, UserManager, dlnaEventManager); + RegisterSingleInstance(contentDirectory); + var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); RegisterSingleInstance(collectionManager);