瀏覽代碼

Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser

Eric Reed 12 年之前
父節點
當前提交
07802a5b6d
共有 24 個文件被更改,包括 362 次插入174 次删除
  1. 35 1
      MediaBrowser.Api/LibraryService.cs
  2. 1 6
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  3. 5 0
      MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
  4. 5 0
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  5. 7 0
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  6. 5 0
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  7. 63 64
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  8. 17 0
      MediaBrowser.Common/Net/IServerManager.cs
  9. 2 1
      MediaBrowser.Controller/Dto/SessionInfoDtoBuilder.cs
  10. 2 1
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  11. 9 6
      MediaBrowser.Controller/Entities/Folder.cs
  12. 1 1
      MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs
  13. 7 1
      MediaBrowser.Controller/Session/SessionInfo.cs
  14. 7 1
      MediaBrowser.Model/Session/SessionInfoDto.cs
  15. 16 3
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  16. 3 44
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  17. 34 4
      MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
  18. 3 2
      MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
  19. 130 34
      MediaBrowser.ServerApplication/EntryPoints/LibraryChangedNotifier.cs
  20. 4 0
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  21. 1 0
      MediaBrowser.ServerApplication/packages.config
  22. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  23. 1 1
      Nuget/MediaBrowser.Common.nuspec
  24. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 35 - 1
MediaBrowser.Api/LibraryService.cs

@@ -96,6 +96,20 @@ namespace MediaBrowser.Api
     {
     }
 
+    [Route("/Items/{Id}/Refresh", "POST")]
+    [Api(Description = "Refreshes metadata for an item")]
+    public class RefreshItem : IReturnVoid
+    {
+        [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
+        public bool Forced { get; set; }
+
+        [ApiMember(Name = "Recursive", Description = "Indicates if the refresh should occur recursively.", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
+        public bool Recursive { get; set; }
+        
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public string Id { get; set; }
+    }
+
     [Route("/Items/Counts", "GET")]
     [Api(Description = "Gets counts of various item types")]
     public class GetItemCounts : IReturn<ItemCounts>
@@ -304,6 +318,26 @@ namespace MediaBrowser.Api
             return ToOptimizedResult(result);
         }
 
+        /// <summary>
+        /// Posts the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        public void Post(RefreshItem request)
+        {
+            var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager);
+
+            var folder = item as Folder;
+
+            if (folder != null)
+            {
+                folder.ValidateChildren(new Progress<double>(), CancellationToken.None, request.Recursive, request.Forced);
+            }
+            else
+            {
+                item.RefreshMetadata(CancellationToken.None, forceRefresh: request.Forced);
+            }
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -467,7 +501,7 @@ namespace MediaBrowser.Api
         {
             var artists1 = item1.RecursiveChildren
                 .OfType<Audio>()
-                .SelectMany(i => new[]{i.AlbumArtist, i.Artist})
+                .SelectMany(i => new[] { i.AlbumArtist, i.Artist })
                 .Where(i => !string.IsNullOrEmpty(i))
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .ToList();

+ 1 - 6
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -426,11 +426,6 @@ namespace MediaBrowser.Api.Playback
         {
             if (audioStream.Channels > 2 && request.AudioCodec.HasValue)
             {
-                if (request.AudioCodec.Value == AudioCodecs.Aac)
-                {
-                    // libvo_aacenc currently only supports two channel output
-                    return 2;
-                }
                 if (request.AudioCodec.Value == AudioCodecs.Wma)
                 {
                     // wmav2 currently only supports two channel output
@@ -465,7 +460,7 @@ namespace MediaBrowser.Api.Playback
             {
                 if (codec == AudioCodecs.Aac)
                 {
-                    return "libvo_aacenc";
+                    return "aac";
                 }
                 if (codec == AudioCodecs.Mp3)
                 {

+ 5 - 0
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -93,6 +93,11 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var args = "-codec:a " + codec;
 
+            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
+            {
+                args += " -strict experimental";
+            }
+            
             var channels = GetNumAudioChannelsParam(state.Request, state.AudioStream);
 
             if (channels.HasValue)

+ 5 - 0
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -98,6 +98,11 @@ namespace MediaBrowser.Api.Playback.Hls
 
             if (state.AudioStream != null)
             {
+                if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
+                {
+                    args += " -strict experimental";
+                }
+                
                 var channels = GetNumAudioChannelsParam(state.Request, state.AudioStream);
 
                 if (channels.HasValue)

+ 7 - 0
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -3,7 +3,9 @@ using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
+using System;
 using System.Collections.Generic;
+using System.IO;
 
 namespace MediaBrowser.Api.Playback.Progressive
 {
@@ -84,6 +86,11 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             var audioTranscodeParams = new List<string>();
 
+            if (string.Equals(Path.GetExtension(outputPath), ".aac", StringComparison.OrdinalIgnoreCase))
+            {
+                audioTranscodeParams.Add("-strict experimental");
+            }
+
             if (request.AudioBitRate.HasValue)
             {
                 audioTranscodeParams.Add("-ab " + request.AudioBitRate.Value);

+ 5 - 0
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -217,6 +217,11 @@ namespace MediaBrowser.Api.Playback.Progressive
             
             var args = "-acodec " + codec;
 
+            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
+            {
+                args += " -strict experimental";
+            }
+            
             // Add the number of audio channels
             var channels = GetNumAudioChannelsParam(request, state.AudioStream);
 

+ 63 - 64
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -160,90 +160,89 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             options.CancellationToken.ThrowIfCancellationRequested();
 
-            using (var message = GetHttpRequestMessage(options))
-            {
-                if (options.EnableResponseCache && cachedInfo != null)
-                {
-                    if (!string.IsNullOrEmpty(cachedInfo.Etag))
-                    {
-                        message.Headers.Add("If-None-Match", cachedInfo.Etag);
-                    }
-                    else if (cachedInfo.LastModified.HasValue)
-                    {
-                        message.Headers.IfModifiedSince = new DateTimeOffset(cachedInfo.LastModified.Value);
-                    }
-                }
+            var message = GetHttpRequestMessage(options);
+
+            //if (options.EnableResponseCache && cachedInfo != null)
+            //{
+            //    if (!string.IsNullOrEmpty(cachedInfo.Etag))
+            //    {
+            //        message.Headers.Add("If-None-Match", cachedInfo.Etag);
+            //    }
+            //    else if (cachedInfo.LastModified.HasValue)
+            //    {
+            //        message.Headers.IfModifiedSince = new DateTimeOffset(cachedInfo.LastModified.Value);
+            //    }
+            //}
 
-                if (options.ResourcePool != null)
-                {
-                    await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
-                }
+            if (options.ResourcePool != null)
+            {
+                await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
+            }
 
-                _logger.Info("HttpClientManager.Get url: {0}", options.Url);
+            _logger.Info("HttpClientManager.Get url: {0}", options.Url);
 
-                try
-                {
-                    options.CancellationToken.ThrowIfCancellationRequested();
+            try
+            {
+                options.CancellationToken.ThrowIfCancellationRequested();
 
-                    var response = await GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false);
+                var response = await GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false);
 
-                    if (options.EnableResponseCache)
+                if (options.EnableResponseCache)
+                {
+                    if (response.StatusCode != HttpStatusCode.NotModified)
                     {
-                        if (response.StatusCode != HttpStatusCode.NotModified)
-                        {
-                            EnsureSuccessStatusCode(response);
-                        }
-
-                        options.CancellationToken.ThrowIfCancellationRequested();
-
-                        cachedInfo = UpdateInfoCache(cachedInfo, options.Url, cachedInfoPath, response);
+                        EnsureSuccessStatusCode(response);
+                    }
 
-                        if (response.StatusCode == HttpStatusCode.NotModified)
-                        {
-                            _logger.Debug("Server indicates not modified for {0}. Returning cached result.", options.Url);
+                    options.CancellationToken.ThrowIfCancellationRequested();
 
-                            return GetCachedResponse(cachedReponsePath);
-                        }
+                    cachedInfo = UpdateInfoCache(cachedInfo, options.Url, cachedInfoPath, response);
 
-                        if (!string.IsNullOrEmpty(cachedInfo.Etag) || cachedInfo.LastModified.HasValue ||
-                            (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > DateTime.UtcNow))
-                        {
-                            await UpdateResponseCache(response, cachedReponsePath).ConfigureAwait(false);
+                    if (response.StatusCode == HttpStatusCode.NotModified)
+                    {
+                        _logger.Debug("Server indicates not modified for {0}. Returning cached result.", options.Url);
 
-                            return GetCachedResponse(cachedReponsePath);
-                        }
+                        return GetCachedResponse(cachedReponsePath);
                     }
-                    else
+
+                    if (!string.IsNullOrEmpty(cachedInfo.Etag) || cachedInfo.LastModified.HasValue ||
+                        (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > DateTime.UtcNow))
                     {
-                        EnsureSuccessStatusCode(response);
+                        await UpdateResponseCache(response, cachedReponsePath).ConfigureAwait(false);
 
-                        options.CancellationToken.ThrowIfCancellationRequested();
+                        return GetCachedResponse(cachedReponsePath);
                     }
-
-                    return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
                 }
-                catch (OperationCanceledException ex)
+                else
                 {
-                    throw GetCancellationException(options.Url, options.CancellationToken, ex);
-                }
-                catch (HttpRequestException ex)
-                {
-                    _logger.ErrorException("Error getting response from " + options.Url, ex);
+                    EnsureSuccessStatusCode(response);
 
-                    throw new HttpException(ex.Message, ex);
+                    options.CancellationToken.ThrowIfCancellationRequested();
                 }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error getting response from " + options.Url, ex);
 
-                    throw;
-                }
-                finally
+                return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+            }
+            catch (OperationCanceledException ex)
+            {
+                throw GetCancellationException(options.Url, options.CancellationToken, ex);
+            }
+            catch (HttpRequestException ex)
+            {
+                _logger.ErrorException("Error getting response from " + options.Url, ex);
+
+                throw new HttpException(ex.Message, ex);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error getting response from " + options.Url, ex);
+
+                throw;
+            }
+            finally
+            {
+                if (options.ResourcePool != null)
                 {
-                    if (options.ResourcePool != null)
-                    {
-                        options.ResourcePool.Release();
-                    }
+                    options.ResourcePool.Release();
                 }
             }
         }

+ 17 - 0
MediaBrowser.Common/Net/IServerManager.cs

@@ -52,10 +52,27 @@ namespace MediaBrowser.Common.Net
         /// <exception cref="System.ArgumentNullException">messageType</exception>
         Task SendWebSocketMessageAsync<T>(string messageType, Func<T> dataFunction, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Sends the web socket message async.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="messageType">Type of the message.</param>
+        /// <param name="dataFunction">The data function.</param>
+        /// <param name="connections">The connections.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendWebSocketMessageAsync<T>(string messageType, Func<T> dataFunction, IEnumerable<IWebSocketConnection> connections, CancellationToken cancellationToken);
+        
         /// <summary>
         /// Adds the web socket listeners.
         /// </summary>
         /// <param name="listeners">The listeners.</param>
         void AddWebSocketListeners(IEnumerable<IWebSocketListener> listeners);
+
+        /// <summary>
+        /// Gets the web socket connections.
+        /// </summary>
+        /// <value>The web socket connections.</value>
+        IEnumerable<IWebSocketConnection> WebSocketConnections { get; }
     }
 }

+ 2 - 1
MediaBrowser.Controller/Dto/SessionInfoDtoBuilder.cs

@@ -26,7 +26,8 @@ namespace MediaBrowser.Controller.Dto
                 SupportsRemoteControl = session.SupportsRemoteControl,
                 IsPaused = session.IsPaused,
                 NowViewingContext = session.NowViewingContext,
-                NowViewingItemIdentifier = session.NowViewingItemIdentifier,
+                NowViewingItemId = session.NowViewingItemId,
+                NowViewingItemName = session.NowViewingItemName,
                 NowViewingItemType = session.NowViewingItemType
             };
 

+ 2 - 1
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -55,8 +55,9 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
+        /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
         /// <returns>Task.</returns>
-        protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null)
+        protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
         {
             //we don't directly validate our children
             //but we do need to clear out the index cache...

+ 9 - 6
MediaBrowser.Controller/Entities/Folder.cs

@@ -554,8 +554,9 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
+        /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
         /// <returns>Task.</returns>
-        public async Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null)
+        public async Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
         {
             cancellationToken.ThrowIfCancellationRequested();
 
@@ -575,7 +576,7 @@ namespace MediaBrowser.Controller.Entities
 
                 var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken);
 
-                await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive).ConfigureAwait(false);
+                await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, forceRefreshMetadata).ConfigureAwait(false);
             }
             catch (OperationCanceledException ex)
             {
@@ -606,8 +607,9 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
+        /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
         /// <returns>Task.</returns>
-        protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null)
+        protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
         {
             // Nothing to do here
             if (LocationType != LocationType.FileSystem)
@@ -723,7 +725,7 @@ namespace MediaBrowser.Controller.Entities
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            await RefreshChildren(validChildren, progress, cancellationToken, recursive).ConfigureAwait(false);
+            await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
 
             progress.Report(100);
         }
@@ -735,8 +737,9 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
+        /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
         /// <returns>Task.</returns>
-        private async Task RefreshChildren(IEnumerable<Tuple<BaseItem, bool>> children, IProgress<double> progress, CancellationToken cancellationToken, bool? recursive)
+        private async Task RefreshChildren(IEnumerable<Tuple<BaseItem, bool>> children, IProgress<double> progress, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false)
         {
             var list = children.ToList();
 
@@ -760,7 +763,7 @@ namespace MediaBrowser.Controller.Entities
                     var child = currentTuple.Item1;
 
                     //refresh it
-                    await child.RefreshMetadata(cancellationToken, resetResolveArgs: child.IsFolder, forceSave: currentTuple.Item2).ConfigureAwait(false);
+                    await child.RefreshMetadata(cancellationToken, resetResolveArgs: child.IsFolder, forceSave: currentTuple.Item2, forceRefresh: forceRefreshMetadata).ConfigureAwait(false);
 
                     // Refresh children if a folder and the item changed or recursive is set to true
                     var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value));

+ 1 - 1
MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs

@@ -83,7 +83,7 @@ namespace MediaBrowser.Controller.Providers.Music
         private string FindIdFromMusicArtistEntity(BaseItem item)
         {
             var artist = _libraryManager.RootFolder.RecursiveChildren.OfType<MusicArtist>()
-                .FirstOrDefault(i => string.Equals(i.Name, item.Name, StringComparison.OrdinalIgnoreCase));
+                .FirstOrDefault(i => string.Compare(i.Name, item.Name, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0);
 
             return artist != null ? artist.GetProviderId(MetadataProviders.Musicbrainz) : null;
         }

+ 7 - 1
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -63,7 +63,13 @@ namespace MediaBrowser.Controller.Session
         /// Gets or sets the now viewing item identifier.
         /// </summary>
         /// <value>The now viewing item identifier.</value>
-        public string NowViewingItemIdentifier { get; set; }
+        public string NowViewingItemId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the now viewing item.
+        /// </summary>
+        /// <value>The name of the now viewing item.</value>
+        public string NowViewingItemName { get; set; }
         
         /// <summary>
         /// Gets or sets the now playing item.

+ 7 - 1
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -45,7 +45,13 @@ namespace MediaBrowser.Model.Session
         /// Gets or sets the now viewing item identifier.
         /// </summary>
         /// <value>The now viewing item identifier.</value>
-        public string NowViewingItemIdentifier { get; set; }
+        public string NowViewingItemId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the now viewing item.
+        /// </summary>
+        /// <value>The name of the now viewing item.</value>
+        public string NowViewingItemName { get; set; }
         
         /// <summary>
         /// Gets or sets the name of the device.

+ 16 - 3
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -32,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.Library
     public class LibraryManager : ILibraryManager
     {
         private IEnumerable<ILibraryPrescanTask> PrescanTasks { get; set; }
-        
+
         /// <summary>
         /// Gets the intro providers.
         /// </summary>
@@ -306,7 +306,20 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <returns>BaseItem.</returns>
         public BaseItem ResolveItem(ItemResolveArgs args)
         {
-            var item = EntityResolvers.Select(r => r.ResolvePath(args)).FirstOrDefault(i => i != null);
+            var item = EntityResolvers.Select(r =>
+            {
+                try
+                {
+                    return r.ResolvePath(args);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error in {0} resolving {1}", ex, r.GetType().Name, args.Path);
+
+                    return null;
+                }
+
+            }).FirstOrDefault(i => i != null);
 
             if (item != null)
             {
@@ -1028,7 +1041,7 @@ namespace MediaBrowser.Server.Implementations.Library
             await SaveItem(item, cancellationToken).ConfigureAwait(false);
 
             UpdateItemInLibraryCache(item);
-            
+
             if (ItemAdded != null)
             {
                 try

+ 3 - 44
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -97,11 +96,6 @@ namespace MediaBrowser.Server.Implementations.Providers
             MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
         }
 
-        /// <summary>
-        /// The _supported providers key
-        /// </summary>
-        private readonly Guid _supportedProvidersKey = "SupportedProviders".GetMD5();
-
         /// <summary>
         /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence
         /// </summary>
@@ -120,40 +114,10 @@ namespace MediaBrowser.Server.Implementations.Providers
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            // Determine if supported providers have changed
-            var supportedProviders = MetadataProviders.Where(p => p.Supports(item)).ToList();
-
-            BaseProviderInfo supportedProvidersInfo;
-
-            var supportedProvidersValue = string.Join(string.Empty, supportedProviders.Select(i => i.GetType().Name));
-            var providersChanged = false;
-
-            item.ProviderData.TryGetValue(_supportedProvidersKey, out supportedProvidersInfo);
-
-            var supportedProvidersHash = supportedProvidersValue.GetMD5();
-
-            if (supportedProvidersInfo != null)
-            {
-                // Force refresh if the supported providers have changed
-                providersChanged = force = force || supportedProvidersHash != supportedProvidersInfo.Data;
-
-                // If providers have changed, clear provider info and update the supported providers hash
-                if (providersChanged)
-                {
-                    _logger.Debug("Providers changed for {0}. Clearing and forcing refresh.", item.Name);
-                    item.ProviderData.Clear();
-                }
-            }
-
-            if (providersChanged)
-            {
-                supportedProvidersInfo.Data = supportedProvidersHash;
-            }
-
             if (force) item.ClearMetaValues();
 
             // Run the normal providers sequentially in order of priority
-            foreach (var provider in supportedProviders)
+            foreach (var provider in MetadataProviders.Where(p => p.Supports(item)))
             {
                 cancellationToken.ThrowIfCancellationRequested();
 
@@ -206,12 +170,7 @@ namespace MediaBrowser.Server.Implementations.Providers
                 result |= results.Contains(true);
             }
 
-            if (providersChanged)
-            {
-                item.ProviderData[_supportedProvidersKey] = supportedProvidersInfo;
-            }
-
-            return result || providersChanged;
+            return result;
         }
 
         /// <summary>

+ 34 - 4
MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs

@@ -36,6 +36,14 @@ namespace MediaBrowser.Server.Implementations.ServerManager
         /// The web socket connections
         /// </summary>
         private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
+        /// <summary>
+        /// Gets the web socket connections.
+        /// </summary>
+        /// <value>The web socket connections.</value>
+        public IEnumerable<IWebSocketConnection> WebSocketConnections
+        {
+            get { return _webSocketConnections; }
+        }
 
         /// <summary>
         /// Gets or sets the external web socket server.
@@ -83,6 +91,9 @@ namespace MediaBrowser.Server.Implementations.ServerManager
         /// <value>The web socket listeners.</value>
         private readonly List<IWebSocketListener> _webSocketListeners = new List<IWebSocketListener>();
 
+        /// <summary>
+        /// The _kernel
+        /// </summary>
         private readonly Kernel _kernel;
 
         /// <summary>
@@ -240,7 +251,26 @@ namespace MediaBrowser.Server.Implementations.ServerManager
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">messageType</exception>
-        public async Task SendWebSocketMessageAsync<T>(string messageType, Func<T> dataFunction, CancellationToken cancellationToken)
+        public Task SendWebSocketMessageAsync<T>(string messageType, Func<T> dataFunction, CancellationToken cancellationToken)
+        {
+            return SendWebSocketMessageAsync(messageType, dataFunction, _webSocketConnections, cancellationToken);
+        }
+
+        /// <summary>
+        /// Sends the web socket message async.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="messageType">Type of the message.</param>
+        /// <param name="dataFunction">The data function.</param>
+        /// <param name="connections">The connections.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">messageType
+        /// or
+        /// dataFunction
+        /// or
+        /// cancellationToken</exception>
+        public async Task SendWebSocketMessageAsync<T>(string messageType, Func<T> dataFunction, IEnumerable<IWebSocketConnection> connections, CancellationToken cancellationToken)
         {
             if (string.IsNullOrEmpty(messageType))
             {
@@ -259,16 +289,16 @@ namespace MediaBrowser.Server.Implementations.ServerManager
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            var connections = _webSocketConnections.Where(s => s.State == WebSocketState.Open).ToList();
+            var connectionsList = connections.Where(s => s.State == WebSocketState.Open).ToList();
 
-            if (connections.Count > 0)
+            if (connectionsList.Count > 0)
             {
                 _logger.Info("Sending web socket message {0}", messageType);
 
                 var message = new WebSocketMessage<T> { MessageType = messageType, Data = dataFunction() };
                 var bytes = _jsonSerializer.SerializeToBytes(message);
 
-                var tasks = connections.Select(s => Task.Run(() =>
+                var tasks = connectionsList.Select(s => Task.Run(() =>
                 {
                     try
                     {

+ 3 - 2
MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -82,8 +82,9 @@ namespace MediaBrowser.Server.Implementations.Session
                     var vals = message.Data.Split('|');
 
                     session.NowViewingItemType = vals[0];
-                    session.NowViewingItemIdentifier = vals[1];
-                    session.NowViewingContext = vals.Length > 2 ? vals[2] : null;
+                    session.NowViewingItemId = vals[1];
+                    session.NowViewingItemName = vals[2];
+                    session.NowViewingContext = vals.Length > 3 ? vals[3] : null;
                 }
             }
             else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase))

+ 130 - 34
MediaBrowser.ServerApplication/EntryPoints/LibraryChangedNotifier.cs

@@ -1,8 +1,12 @@
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Entities;
+using MoreLinq;
+using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 
@@ -17,17 +21,19 @@ namespace MediaBrowser.ServerApplication.EntryPoints
 
         private readonly ISessionManager _sessionManager;
         private readonly IServerManager _serverManager;
-   
+        private readonly IUserManager _userManager;
+
         /// <summary>
         /// The _library changed sync lock
         /// </summary>
         private readonly object _libraryChangedSyncLock = new object();
 
-        /// <summary>
-        /// Gets or sets the library update info.
-        /// </summary>
-        /// <value>The library update info.</value>
-        private LibraryUpdateInfo LibraryUpdateInfo { get; set; }
+        private readonly List<Folder> _foldersAddedTo = new List<Folder>();
+        private readonly List<Folder> _foldersRemovedFrom = new List<Folder>();
+
+        private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
+        private readonly List<BaseItem> _itemsRemoved = new List<BaseItem>();
+        private readonly List<BaseItem> _itemsUpdated = new List<BaseItem>();
 
         /// <summary>
         /// Gets or sets the library update timer.
@@ -40,11 +46,12 @@ namespace MediaBrowser.ServerApplication.EntryPoints
         /// </summary>
         private const int LibraryUpdateDuration = 60000;
 
-        public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IServerManager serverManager)
+        public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IServerManager serverManager, IUserManager userManager)
         {
             _libraryManager = libraryManager;
             _sessionManager = sessionManager;
             _serverManager = serverManager;
+            _userManager = userManager;
         }
 
         public void Run()
@@ -64,11 +71,6 @@ namespace MediaBrowser.ServerApplication.EntryPoints
         {
             lock (_libraryChangedSyncLock)
             {
-                if (LibraryUpdateInfo == null)
-                {
-                    LibraryUpdateInfo = new LibraryUpdateInfo();
-                }
-
                 if (LibraryUpdateTimer == null)
                 {
                     LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
@@ -81,10 +83,10 @@ namespace MediaBrowser.ServerApplication.EntryPoints
 
                 if (e.Item.Parent != null)
                 {
-                    LibraryUpdateInfo.FoldersAddedTo.Add(e.Item.Parent.Id);
+                    _foldersAddedTo.Add(e.Item.Parent);
                 }
 
-                LibraryUpdateInfo.ItemsAdded.Add(e.Item.Id);
+                _itemsAdded.Add(e.Item);
             }
         }
 
@@ -97,11 +99,6 @@ namespace MediaBrowser.ServerApplication.EntryPoints
         {
             lock (_libraryChangedSyncLock)
             {
-                if (LibraryUpdateInfo == null)
-                {
-                    LibraryUpdateInfo = new LibraryUpdateInfo();
-                }
-
                 if (LibraryUpdateTimer == null)
                 {
                     LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
@@ -112,7 +109,7 @@ namespace MediaBrowser.ServerApplication.EntryPoints
                     LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite);
                 }
 
-                LibraryUpdateInfo.ItemsUpdated.Add(e.Item.Id);
+                _itemsUpdated.Add(e.Item);
             }
         }
 
@@ -125,11 +122,6 @@ namespace MediaBrowser.ServerApplication.EntryPoints
         {
             lock (_libraryChangedSyncLock)
             {
-                if (LibraryUpdateInfo == null)
-                {
-                    LibraryUpdateInfo = new LibraryUpdateInfo();
-                }
-
                 if (LibraryUpdateTimer == null)
                 {
                     LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
@@ -142,10 +134,10 @@ namespace MediaBrowser.ServerApplication.EntryPoints
 
                 if (e.Item.Parent != null)
                 {
-                    LibraryUpdateInfo.FoldersRemovedFrom.Add(e.Item.Parent.Id);
+                    _foldersRemovedFrom.Add(e.Item.Parent);
                 }
 
-                LibraryUpdateInfo.ItemsRemoved.Add(e.Item.Id);
+                _itemsRemoved.Add(e.Item);
             }
         }
 
@@ -158,16 +150,16 @@ namespace MediaBrowser.ServerApplication.EntryPoints
             lock (_libraryChangedSyncLock)
             {
                 // Remove dupes in case some were saved multiple times
-                LibraryUpdateInfo.FoldersAddedTo = LibraryUpdateInfo.FoldersAddedTo.Distinct().ToList();
+                var foldersAddedTo = _foldersAddedTo.DistinctBy(i => i.Id).ToList();
 
-                LibraryUpdateInfo.FoldersRemovedFrom = LibraryUpdateInfo.FoldersRemovedFrom.Distinct().ToList();
+                var foldersRemovedFrom = _foldersRemovedFrom.DistinctBy(i => i.Id).ToList();
 
-                LibraryUpdateInfo.ItemsUpdated = LibraryUpdateInfo.ItemsUpdated
-                    .Where(i => !LibraryUpdateInfo.ItemsAdded.Contains(i))
-                    .Distinct()
+                var itemsUpdated = _itemsUpdated
+                    .Where(i => !_itemsAdded.Contains(i))
+                    .DistinctBy(i => i.Id)
                     .ToList();
 
-                _serverManager.SendWebSocketMessage("LibraryChanged", LibraryUpdateInfo);
+                SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None);
 
                 if (LibraryUpdateTimer != null)
                 {
@@ -175,8 +167,112 @@ namespace MediaBrowser.ServerApplication.EntryPoints
                     LibraryUpdateTimer = null;
                 }
 
-                LibraryUpdateInfo = null;
+                _itemsAdded.Clear();
+                _itemsRemoved.Clear();
+                _itemsUpdated.Clear();
+                _foldersAddedTo.Clear();
+                _foldersRemovedFrom.Clear();
+            }
+        }
+
+        /// <summary>
+        /// Sends the change notifications.
+        /// </summary>
+        /// <param name="itemsAdded">The items added.</param>
+        /// <param name="itemsUpdated">The items updated.</param>
+        /// <param name="itemsRemoved">The items removed.</param>
+        /// <param name="foldersAddedTo">The folders added to.</param>
+        /// <param name="foldersRemovedFrom">The folders removed from.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        private async void SendChangeNotifications(IEnumerable<BaseItem> itemsAdded, IEnumerable<BaseItem> itemsUpdated, IEnumerable<BaseItem> itemsRemoved, IEnumerable<Folder> foldersAddedTo, IEnumerable<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
+        {
+            var currentSessions = _sessionManager.Sessions.ToList();
+
+            var users = currentSessions.Select(i => i.UserId ?? Guid.Empty).Where(i => i != Guid.Empty).Distinct().ToList();
+
+            foreach (var userId in users)
+            {
+                var id = userId;
+                var webSockets = currentSessions.Where(u => u.UserId.HasValue && u.UserId.Value == id).SelectMany(i => i.WebSockets).ToList();
+
+                await _serverManager.SendWebSocketMessageAsync("LibraryChanged", () => GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, id), webSockets, cancellationToken).ConfigureAwait(false);
+            }
+        }
+
+        /// <summary>
+        /// Gets the library update info.
+        /// </summary>
+        /// <param name="itemsAdded">The items added.</param>
+        /// <param name="itemsUpdated">The items updated.</param>
+        /// <param name="itemsRemoved">The items removed.</param>
+        /// <param name="foldersAddedTo">The folders added to.</param>
+        /// <param name="foldersRemovedFrom">The folders removed from.</param>
+        /// <param name="userId">The user id.</param>
+        /// <returns>LibraryUpdateInfo.</returns>
+        private LibraryUpdateInfo GetLibraryUpdateInfo(IEnumerable<BaseItem> itemsAdded, IEnumerable<BaseItem> itemsUpdated, IEnumerable<BaseItem> itemsRemoved, IEnumerable<Folder> foldersAddedTo, IEnumerable<Folder> foldersRemovedFrom, Guid userId)
+        {
+            var user = _userManager.GetUserById(userId);
+
+            var collections = user.RootFolder.GetChildren(user).ToList();
+
+            var allRecursiveChildren = user.RootFolder.GetRecursiveChildren(user).ToDictionary(i => i.Id);
+
+            return new LibraryUpdateInfo
+            {
+                ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, collections, allRecursiveChildren)).Select(i => i.Id).Distinct().ToList(),
+
+                ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, collections, allRecursiveChildren)).Select(i => i.Id).Distinct().ToList(),
+
+                ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, collections, allRecursiveChildren)).Select(i => i.Id).Distinct().ToList(),
+
+                FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, collections, allRecursiveChildren)).Select(i => i.Id).Distinct().ToList(),
+
+                FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, collections, allRecursiveChildren)).Select(i => i.Id).Distinct().ToList()
+            };
+        }
+
+        /// <summary>
+        /// Translates the physical item to user library.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="item">The item.</param>
+        /// <param name="user">The user.</param>
+        /// <param name="collections">The collections.</param>
+        /// <param name="allRecursiveChildren">All recursive children.</param>
+        /// <returns>IEnumerable{``0}.</returns>
+        private IEnumerable<T> TranslatePhysicalItemToUserLibrary<T>(T item, User user, List<BaseItem> collections, Dictionary<Guid, BaseItem> allRecursiveChildren)
+            where T : BaseItem
+        {
+            // If the physical root changed, return the user root
+            if (item is AggregateFolder)
+            {
+                return new T[] { user.RootFolder as T };
+            }
+
+            // Need to find what user collection folder this belongs to
+            if (item.Parent is AggregateFolder)
+            {
+                return new T[] { user.RootFolder as T };
+            }
+
+            // If it's a user root, return it only if it's the right one
+            if (item is UserRootFolder)
+            {
+                if (item.Id == user.RootFolder.Id)
+                {
+                    return new T[] { item };
+                }
+
+                return new T[] { };
             }
+
+            // Return it only if it's in the user's library
+            if (allRecursiveChildren.ContainsKey(item.Id))
+            {
+                return new T[] { item };
+            }
+
+            return new T[] { };
         }
 
         /// <summary>

+ 4 - 0
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -130,6 +130,10 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.51\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>
     </Reference>
+    <Reference Include="MoreLinq, Version=1.0.15631.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\morelinq.1.0.15631-beta\lib\net35\MoreLinq.dll</HintPath>
+    </Reference>
     <Reference Include="NLog, Version=2.0.1.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\NLog.2.0.1.2\lib\net45\NLog.dll</HintPath>

+ 1 - 0
MediaBrowser.ServerApplication/packages.config

@@ -4,6 +4,7 @@
   <package id="Hardcodet.Wpf.TaskbarNotification" version="1.0.4.0" targetFramework="net45" />
   <package id="MahApps.Metro" version="0.11.0.17-ALPHA" targetFramework="net45" />
   <package id="MediaBrowser.IsoMounting" version="3.0.51" targetFramework="net45" />
+  <package id="morelinq" version="1.0.15631-beta" targetFramework="net45" />
   <package id="NLog" version="2.0.1.2" targetFramework="net45" />
   <package id="ServiceStack" version="3.9.46" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.46" targetFramework="net45" />

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.104</version>
+        <version>3.0.105</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.104" />
+            <dependency id="MediaBrowser.Common" version="3.0.105" />
             <dependency id="NLog" version="2.0.1.2" />
             <dependency id="ServiceStack.Text" version="3.9.45" />
             <dependency id="SimpleInjector" version="2.2.1" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.104</version>
+        <version>3.0.105</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.104</version>
+        <version>3.0.105</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.104" />
+            <dependency id="MediaBrowser.Common" version="3.0.105" />
         </dependencies>
     </metadata>
     <files>