Bladeren bron

support sending upnp events

Luke Pulverenti 11 jaren geleden
bovenliggende
commit
4331700747

+ 13 - 34
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");

+ 1 - 1
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);
 

+ 8 - 0
MediaBrowser.Common/Net/IHttpClient.cs

@@ -43,6 +43,14 @@ namespace MediaBrowser.Common.Net
         /// <returns>Task{Stream}.</returns>
         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>

+ 17 - 0
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>();
+        }
+    }
+}

+ 21 - 0
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);
+    }
+}

+ 0 - 14
MediaBrowser.Controller/Dlna/IDlnaManager.cs

@@ -64,20 +64,6 @@ namespace MediaBrowser.Controller.Dlna
         /// <returns>System.String.</returns>
         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>

+ 47 - 0
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);
+    }
+}

+ 3 - 0
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" />

+ 8 - 79
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();
-        }
     }
 }

+ 171 - 0
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();
+            }
+        }
+    }
+}

+ 2 - 0
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" />

+ 9 - 0
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",

+ 9 - 0
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",

+ 151 - 0
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;
+            }
+        }
+    }
+}

+ 6 - 5
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))
             };
         }
 

+ 3 - 14
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>");

+ 9 - 4
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);
         }
 

+ 3 - 0
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>

+ 3 - 0
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>

+ 34 - 0
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;
+            }
+        }
+    }
+}

+ 1 - 0
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" />

+ 9 - 1
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);