Browse Source

add dynamic images

Luke Pulverenti 10 years ago
parent
commit
dbb7fd61ff

+ 1 - 0
MediaBrowser.Api/StartupWizardService.cs

@@ -65,6 +65,7 @@ namespace MediaBrowser.Api
             _config.Configuration.MergeMetadataAndImagesByName = true;
             _config.Configuration.EnableStandaloneMetadata = true;
             _config.Configuration.EnableLibraryMetadataSubFolder = true;
+            _config.Configuration.EnableUserSpecificUserViews = true;
             _config.SaveConfiguration();
         }
 

+ 4 - 2
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -125,13 +125,15 @@ namespace MediaBrowser.Api.Subtitles
         private readonly ISubtitleManager _subtitleManager;
         private readonly ISubtitleEncoder _subtitleEncoder;
         private readonly IMediaSourceManager _mediaSourceManager;
+        private readonly IProviderManager _providerManager;
 
-        public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager)
+        public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProviderManager providerManager)
         {
             _libraryManager = libraryManager;
             _subtitleManager = subtitleManager;
             _subtitleEncoder = subtitleEncoder;
             _mediaSourceManager = mediaSourceManager;
+            _providerManager = providerManager;
         }
 
         public object Get(GetSubtitlePlaylist request)
@@ -256,7 +258,7 @@ namespace MediaBrowser.Api.Subtitles
                     await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None)
                         .ConfigureAwait(false);
 
-                    await video.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService()), CancellationToken.None).ConfigureAwait(false);
+                    _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions());
                 }
                 catch (Exception ex)
                 {

+ 3 - 3
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -244,7 +244,7 @@ namespace MediaBrowser.Api.UserLibrary
             return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
         }
 
-        public IEnumerable<VideoType> GetVideoTypes()
+        public VideoType[] GetVideoTypes()
         {
             var val = VideoTypes;
 
@@ -253,7 +253,7 @@ namespace MediaBrowser.Api.UserLibrary
                 return new VideoType[] { };
             }
 
-            return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true));
+            return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
         }
     }
 
@@ -933,7 +933,7 @@ namespace MediaBrowser.Api.UserLibrary
 
                 // Filter by VideoType
                 var videoTypes = request.GetVideoTypes();
-                if (video == null || !videoTypes.Contains(video.VideoType))
+                if (videoTypes.Length > 0 && (video == null || !videoTypes.Contains(video.VideoType)))
                 {
                     return false;
                 }

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

@@ -1455,7 +1455,8 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>Task.</returns>
         public virtual Task ChangedExternally()
         {
-            return RefreshMetadata(CancellationToken.None);
+            ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions());
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 4 - 2
MediaBrowser.Controller/Entities/UserView.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.TV;
+using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Controller.TV;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using System;
@@ -15,6 +16,7 @@ namespace MediaBrowser.Controller.Entities
         public Guid? UserId { get; set; }
 
         public static ITVSeriesManager TVSeriesManager;
+        public static IPlaylistManager PlaylistManager;
 
         public bool ContainsDynamicCategories(User user)
         {
@@ -30,7 +32,7 @@ namespace MediaBrowser.Controller.Entities
                 parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
             }
 
-            return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager)
+            return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager, PlaylistManager)
                 .GetUserItems(parent, this, ViewType, query);
         }
 

+ 15 - 4
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Controller.TV;
 using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Entities;
@@ -30,8 +31,9 @@ namespace MediaBrowser.Controller.Entities
         private readonly IUserDataManager _userDataManager;
         private readonly ITVSeriesManager _tvSeriesManager;
         private readonly ICollectionManager _collectionManager;
+        private readonly IPlaylistManager _playlistManager;
 
-        public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, ICollectionManager collectionManager)
+        public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, ICollectionManager collectionManager, IPlaylistManager playlistManager)
         {
             _userViewManager = userViewManager;
             _liveTvManager = liveTvManager;
@@ -41,6 +43,7 @@ namespace MediaBrowser.Controller.Entities
             _userDataManager = userDataManager;
             _tvSeriesManager = tvSeriesManager;
             _collectionManager = collectionManager;
+            _playlistManager = playlistManager;
         }
 
         public async Task<QueryResult<BaseItem>> GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query)
@@ -115,6 +118,9 @@ namespace MediaBrowser.Controller.Entities
                 case CollectionType.Games:
                     return await GetGameView(user, queryParent, query).ConfigureAwait(false);
 
+                case CollectionType.Playlists:
+                    return await GetPlaylistsView(queryParent, user, query).ConfigureAwait(false);
+
                 case CollectionType.BoxSets:
                     return await GetBoxsetView(queryParent, user, query).ConfigureAwait(false);
 
@@ -572,6 +578,11 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(items, queryParent, query);
         }
 
+        private async Task<QueryResult<BaseItem>> GetPlaylistsView(Folder parent, User user, InternalItemsQuery query)
+        {
+            return GetResult(_playlistManager.GetPlaylists(user.Id.ToString("N")), parent, query);
+        }
+
         private async Task<QueryResult<BaseItem>> GetBoxsetView(Folder parent, User user, InternalItemsQuery query)
         {
             return GetResult(GetMediaFolders(user).SelectMany(i =>
@@ -1718,7 +1729,7 @@ namespace MediaBrowser.Controller.Entities
 
             var parent = user.RootFolder;
 
-            //list.Add(await GetUserView(SpecialFolder.LiveTvNowPlaying, user, "0", parent).ConfigureAwait(false));
+            //list.Add(await GetUserSubView(SpecialFolder.LiveTvNowPlaying, user, "0", parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.LiveTvChannels, user, string.Empty, parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, user, string.Empty, parent).ConfigureAwait(false));
 
@@ -1727,7 +1738,7 @@ namespace MediaBrowser.Controller.Entities
 
         private async Task<UserView> GetUserView(string name, string type, User user, string sortName, BaseItem parent)
         {
-            var view = await _userViewManager.GetUserView(name, parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
+            var view = await _userViewManager.GetUserSubView(name, parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
                         .ConfigureAwait(false);
 
             return view;
@@ -1735,7 +1746,7 @@ namespace MediaBrowser.Controller.Entities
 
         private async Task<UserView> GetUserView(string type, User user, string sortName, BaseItem parent)
         {
-            var view = await _userViewManager.GetUserView(parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
+            var view = await _userViewManager.GetUserSubView(parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
                         .ConfigureAwait(false);
 
             return view;

+ 5 - 3
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -302,7 +302,7 @@ namespace MediaBrowser.Controller.Library
         IEnumerable<BaseItem> ReplaceVideosWithPrimaryVersions(IEnumerable<BaseItem> items);
 
         /// <summary>
-        /// Gets the special folder.
+        /// Gets the named view.
         /// </summary>
         /// <param name="user">The user.</param>
         /// <param name="name">The name.</param>
@@ -311,7 +311,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="sortName">Name of the sort.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;UserView&gt;.</returns>
-        Task<UserView> GetSpecialFolder(User user,
+        Task<UserView> GetNamedView(User user,
             string name,
             string parentId,
             string viewType, 
@@ -321,12 +321,14 @@ namespace MediaBrowser.Controller.Library
         /// <summary>
         /// Gets the named view.
         /// </summary>
+        /// <param name="user">The user.</param>
         /// <param name="name">The name.</param>
         /// <param name="viewType">Type of the view.</param>
         /// <param name="sortName">Name of the sort.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;UserView&gt;.</returns>
-        Task<UserView> GetNamedView(string name, 
+        Task<UserView> GetNamedView(User user,
+            string name, 
             string viewType, 
             string sortName, 
             CancellationToken cancellationToken);

+ 2 - 4
MediaBrowser.Controller/Library/IUserViewManager.cs

@@ -12,12 +12,10 @@ namespace MediaBrowser.Controller.Library
     {
         Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken);
 
-        Task<UserView> GetUserView(string name, string parentId, string type, User user, string sortName,
+        Task<UserView> GetUserSubView(string name, string parentId, string type, User user, string sortName,
             CancellationToken cancellationToken);
 
-        Task<UserView> GetUserView(string type, string sortName, CancellationToken cancellationToken);
-
-        Task<UserView> GetUserView(string category, string type, User user, string sortName, CancellationToken cancellationToken);
+        Task<UserView> GetUserSubView(string category, string type, User user, string sortName, CancellationToken cancellationToken);
 
         List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request);
     }

+ 8 - 0
MediaBrowser.Controller/Providers/IProviderManager.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Threading;
@@ -16,6 +17,13 @@ namespace MediaBrowser.Controller.Providers
     /// </summary>
     public interface IProviderManager
     {
+        /// <summary>
+        /// Queues the refresh.
+        /// </summary>
+        /// <param name="itemId">The item identifier.</param>
+        /// <param name="options">The options.</param>
+        void QueueRefresh(Guid itemId, MetadataRefreshOptions options);
+
         /// <summary>
         /// Refreshes the metadata.
         /// </summary>

+ 2 - 2
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -251,12 +251,12 @@ namespace MediaBrowser.Model.ApiClient
         Task<ItemsResult> GetAdditionalParts(string itemId, string userId);
 
         /// <summary>
-        /// Gets the live media information.
+        /// Gets the playback information.
         /// </summary>
         /// <param name="itemId">The item identifier.</param>
         /// <param name="userId">The user identifier.</param>
         /// <returns>Task&lt;LiveMediaInfoResult&gt;.</returns>
-        Task<LiveMediaInfoResult> GetLiveMediaInfo(string itemId, string userId);
+        Task<LiveMediaInfoResult> GetPlaybackInfo(string itemId, string userId);
 
         /// <summary>
         /// Gets the users async.

+ 6 - 0
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -44,6 +44,12 @@ namespace MediaBrowser.Model.Configuration
         /// <value><c>true</c> if [use HTTPS]; otherwise, <c>false</c>.</value>
         public bool EnableHttps { get; set; }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether [enable user specific user views].
+        /// </summary>
+        /// <value><c>true</c> if [enable user specific user views]; otherwise, <c>false</c>.</value>
+        public bool EnableUserSpecificUserViews { get; set; }
+        
         /// <summary>
         /// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
         /// </summary>

+ 5 - 5
MediaBrowser.Providers/FolderImages/DefaultImageProvider.cs

@@ -36,16 +36,16 @@ namespace MediaBrowser.Providers.FolderImages
 
             if (view != null)
             {
-                return GetImages(view.ViewType, view.UserId.HasValue, cancellationToken);
+                return GetImages(view.ViewType, view.ParentId != Guid.Empty, cancellationToken);
             }
 
             var folder = (ICollectionFolder)item;
             return GetImages(folder.CollectionType, false, cancellationToken);
         }
 
-        private Task<IEnumerable<RemoteImageInfo>> GetImages(string viewType, bool isUserSpecificView, CancellationToken cancellationToken)
+        private Task<IEnumerable<RemoteImageInfo>> GetImages(string viewType, bool isSubView, CancellationToken cancellationToken)
         {
-            var url = GetImageUrl(viewType, isUserSpecificView);
+            var url = GetImageUrl(viewType, isSubView);
             var list = new List<RemoteImageInfo>();
 
             if (!string.IsNullOrWhiteSpace(url))
@@ -71,7 +71,7 @@ namespace MediaBrowser.Providers.FolderImages
             return Task.FromResult<IEnumerable<RemoteImageInfo>>(list);
         }
 
-        private string GetImageUrl(string viewType, bool isUserSpecificView)
+        private string GetImageUrl(string viewType, bool isSubView)
         {
             const string urlPrefix = "https://raw.githubusercontent.com/MediaBrowser/MediaBrowser.Resources/master/images/folders/";
 
@@ -108,7 +108,7 @@ namespace MediaBrowser.Providers.FolderImages
                 return urlPrefix + "movies.png";
             }
 
-            if (isUserSpecificView)
+            if (isSubView)
             {
                 return null;
             }

+ 80 - 2
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -26,7 +26,7 @@ namespace MediaBrowser.Providers.Manager
     /// <summary>
     /// Class ProviderManager
     /// </summary>
-    public class ProviderManager : IProviderManager
+    public class ProviderManager : IProviderManager, IDisposable
     {
         /// <summary>
         /// The _logger
@@ -63,6 +63,8 @@ namespace MediaBrowser.Providers.Manager
 
         private IExternalId[] _externalIds;
 
+        private readonly Func<ILibraryManager> _libraryManagerFactory;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
         /// </summary>
@@ -71,7 +73,7 @@ namespace MediaBrowser.Providers.Manager
         /// <param name="libraryMonitor">The directory watchers.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="fileSystem">The file system.</param>
-        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths)
+        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory)
         {
             _logger = logManager.GetLogger("ProviderManager");
             _httpClient = httpClient;
@@ -79,6 +81,7 @@ namespace MediaBrowser.Providers.Manager
             _libraryMonitor = libraryMonitor;
             _fileSystem = fileSystem;
             _appPaths = appPaths;
+            _libraryManagerFactory = libraryManagerFactory;
         }
 
         /// <summary>
@@ -841,5 +844,80 @@ namespace MediaBrowser.Providers.Manager
 
                 });
         }
+
+        private readonly ConcurrentQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
+            new ConcurrentQueue<Tuple<Guid, MetadataRefreshOptions>>();
+
+        private readonly object _refreshTimerLock = new object();
+        private Timer _refreshTimer;
+
+        public void QueueRefresh(Guid id, MetadataRefreshOptions options)
+        {
+            if (_disposed)
+            {
+                return;
+            }
+
+            _refreshQueue.Enqueue(new Tuple<Guid, MetadataRefreshOptions>(id, options));
+            StartRefreshTimer();
+        }
+
+        private void StartRefreshTimer()
+        {
+            lock (_refreshTimerLock)
+            {
+                if (_refreshTimer == null)
+                {
+                    _refreshTimer = new Timer(RefreshTimerCallback, null, 100, Timeout.Infinite);
+                }
+            }
+        }
+
+        private void StopRefreshTimer()
+        {
+            lock (_refreshTimerLock)
+            {
+                if (_refreshTimer != null)
+                {
+                    _refreshTimer.Dispose();
+                    _refreshTimer = null;
+                }
+            }
+        }
+
+        private async void RefreshTimerCallback(object state)
+        {
+            Tuple<Guid, MetadataRefreshOptions> refreshItem;
+            var libraryManager = _libraryManagerFactory();
+
+            while (_refreshQueue.TryDequeue(out refreshItem))
+            {
+                if (_disposed)
+                {
+                    return;
+                }
+
+                try
+                {
+                    var item = libraryManager.GetItemById(refreshItem.Item1);
+                    if (item != null)
+                    {
+                        await item.RefreshMetadata(refreshItem.Item2, CancellationToken.None).ConfigureAwait(false);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error refreshing item", ex);
+                }
+            }
+
+            StopRefreshTimer();
+        }
+
+        private bool _disposed;
+        public void Dispose()
+        {
+            _disposed = true;
+        }
     }
 }

+ 3 - 1
MediaBrowser.Server.Implementations/Channels/ChannelManager.cs

@@ -1400,7 +1400,9 @@ namespace MediaBrowser.Server.Implementations.Channels
         public async Task<Folder> GetInternalChannelFolder(string userId, CancellationToken cancellationToken)
         {
             var name = _localization.GetLocalizedString("ViewTypeChannels");
-            return await _libraryManager.GetNamedView(name, "channels", "zz_" + name, cancellationToken).ConfigureAwait(false);
+            var user = _userManager.GetUserById(userId);
+
+            return await _libraryManager.GetNamedView(user, name, "channels", "zz_" + name, cancellationToken).ConfigureAwait(false);
         }
 
         public async Task DownloadChannelItem(IChannelMediaItem item, string destination,

+ 40 - 25
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -1584,15 +1584,22 @@ namespace MediaBrowser.Server.Implementations.Library
                 .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
         }
 
-        public async Task<UserView> GetNamedView(string name,
-            string type,
+        public async Task<UserView> GetNamedView(User user,
+            string name,
+            string viewType,
             string sortName,
             CancellationToken cancellationToken)
         {
+            if (ConfigurationManager.Configuration.EnableUserSpecificUserViews)
+            {
+                return await GetNamedViewInternal(user, name, null, viewType, sortName, cancellationToken)
+                            .ConfigureAwait(false);
+            }
+
             var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath,
                             "views");
 
-            path = Path.Combine(path, _fileSystem.GetValidFilename(type));
+            path = Path.Combine(path, _fileSystem.GetValidFilename(viewType));
 
             var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
 
@@ -1611,7 +1618,7 @@ namespace MediaBrowser.Server.Implementations.Library
                     Id = id,
                     DateCreated = DateTime.UtcNow,
                     Name = name,
-                    ViewType = type,
+                    ViewType = viewType,
                     ForcedSortName = sortName
                 };
 
@@ -1627,31 +1634,38 @@ namespace MediaBrowser.Server.Implementations.Library
 
             if (refresh)
             {
-                await item.RefreshMetadata(new MetadataRefreshOptions
-                {
-                    ForceSave = true
-
-                }, cancellationToken).ConfigureAwait(false);
+                await item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None).ConfigureAwait(false);
+                _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions());
             }
 
             return item;
         }
 
-        public async Task<UserView> GetSpecialFolder(User user,
+        public Task<UserView> GetNamedView(User user,
             string name,
             string parentId,
             string viewType,
             string sortName,
             CancellationToken cancellationToken)
         {
-            if (string.IsNullOrWhiteSpace(name))
+            if (string.IsNullOrWhiteSpace(parentId))
             {
-                throw new ArgumentNullException("name");
+                throw new ArgumentNullException("parentId");
             }
 
-            if (string.IsNullOrWhiteSpace(parentId))
+            return GetNamedViewInternal(user, name, parentId, viewType, sortName, cancellationToken);
+        }
+
+        private async Task<UserView> GetNamedViewInternal(User user,
+            string name,
+            string parentId,
+            string viewType,
+            string sortName,
+            CancellationToken cancellationToken)
+        {
+            if (string.IsNullOrWhiteSpace(name))
             {
-                throw new ArgumentNullException("parentId");
+                throw new ArgumentNullException("name");
             }
 
             if (string.IsNullOrWhiteSpace(viewType))
@@ -1659,9 +1673,9 @@ namespace MediaBrowser.Server.Implementations.Library
                 throw new ArgumentNullException("viewType");
             }
 
-            var id = GetNewItemId("7_namedview_" + name + user.Id.ToString("N") + parentId, typeof(UserView));
+            var id = GetNewItemId("23_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty), typeof(UserView));
 
-            var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", "specialviews", id.ToString("N"));
+            var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
 
             var item = GetItemById(id) as UserView;
 
@@ -1679,27 +1693,28 @@ namespace MediaBrowser.Server.Implementations.Library
                     Name = name,
                     ViewType = viewType,
                     ForcedSortName = sortName,
-                    UserId = user.Id,
-                    ParentId = new Guid(parentId)
+                    UserId = user.Id
                 };
 
+                if (!string.IsNullOrWhiteSpace(parentId))
+                {
+                    item.ParentId = new Guid(parentId);
+                }
+
                 await CreateItem(item, cancellationToken).ConfigureAwait(false);
 
                 refresh = true;
             }
 
-            if (!refresh && item != null)
+            if (!refresh)
             {
                 refresh = (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 24;
             }
 
             if (refresh)
             {
-                await item.RefreshMetadata(new MetadataRefreshOptions
-                {
-                    ForceSave = true
-
-                }, cancellationToken).ConfigureAwait(false);
+                await item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None).ConfigureAwait(false);
+                _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions());
             }
 
             return item;
@@ -1849,7 +1864,7 @@ namespace MediaBrowser.Server.Implementations.Library
             // These cause apps to have problems
             options.AudioFileExtensions.Remove(".m3u");
             options.AudioFileExtensions.Remove(".wpl");
-        
+
             if (!ConfigurationManager.Configuration.EnableAudioArchiveFiles)
             {
                 options.AudioFileExtensions.Remove(".rar");

+ 14 - 13
MediaBrowser.Server.Implementations/Library/UserViewManager.cs

@@ -70,40 +70,41 @@ namespace MediaBrowser.Server.Implementations.Library
             if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) ||
                 foldersWithViewTypes.Any(i => string.IsNullOrWhiteSpace(i.CollectionType)))
             {
-                list.Add(await GetUserView(CollectionType.TvShows, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.TvShows, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
 
             if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) ||
                 foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)))
             {
-                list.Add(await GetUserView(CollectionType.Music, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.Music, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
 
             if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) ||
                 foldersWithViewTypes.Any(i => string.IsNullOrWhiteSpace(i.CollectionType)))
             {
-                list.Add(await GetUserView(CollectionType.Movies, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.Movies, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
 
             if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Games, StringComparison.OrdinalIgnoreCase)))
             {
-                list.Add(await GetUserView(CollectionType.Games, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.Games, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
 
             if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)))
             {
                 //list.Add(_collectionManager.GetCollectionsFolder(user.Id.ToString("N")));
-                list.Add(await GetUserView(CollectionType.BoxSets, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.BoxSets, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
 
             if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)))
             {
-                list.Add(_playlists.GetPlaylistsFolder(user.Id.ToString("N")));
+                //list.Add(_playlists.GetPlaylistsFolder(user.Id.ToString("N")));
+                list.Add(await GetUserView(CollectionType.Playlists, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
 
             if (user.Configuration.DisplayFoldersView)
             {
-                list.Add(await GetUserView(CollectionType.Folders, "zz_" + CollectionType.Folders, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.Folders, "zz_" + CollectionType.Folders, user, cancellationToken).ConfigureAwait(false));
             }
 
             if (query.IncludeExternalContent)
@@ -148,23 +149,23 @@ namespace MediaBrowser.Server.Implementations.Library
                 .ThenBy(i => i.SortName);
         }
 
-        public Task<UserView> GetUserView(string name, string parentId, string type, User user, string sortName, CancellationToken cancellationToken)
+        public Task<UserView> GetUserSubView(string name, string parentId, string type, User user, string sortName, CancellationToken cancellationToken)
         {
-            return _libraryManager.GetSpecialFolder(user, name, parentId, type, sortName, cancellationToken);
+            return _libraryManager.GetNamedView(user, name, parentId, type, sortName, cancellationToken);
         }
 
-        public Task<UserView> GetUserView(string parentId, string type, User user, string sortName, CancellationToken cancellationToken)
+        public Task<UserView> GetUserSubView(string parentId, string type, User user, string sortName, CancellationToken cancellationToken)
         {
             var name = _localizationManager.GetLocalizedString("ViewType" + type);
 
-            return GetUserView(name, parentId, type, user, sortName, cancellationToken);
+            return GetUserSubView(name, parentId, type, user, sortName, cancellationToken);
         }
 
-        public Task<UserView> GetUserView(string type, string sortName, CancellationToken cancellationToken)
+        public Task<UserView> GetUserView(string type, string sortName, User user, CancellationToken cancellationToken)
         {
             var name = _localizationManager.GetLocalizedString("ViewType" + type);
 
-            return _libraryManager.GetNamedView(name, type, sortName, cancellationToken);
+            return _libraryManager.GetNamedView(user, name, type, sortName, cancellationToken);
         }
 
         public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request)

+ 2 - 1
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -1827,7 +1827,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         public async Task<Folder> GetInternalLiveTvFolder(string userId, CancellationToken cancellationToken)
         {
             var name = _localization.GetLocalizedString("ViewTypeLiveTV");
-            return await _libraryManager.GetNamedView(name, "livetv", "zz_" + name, cancellationToken).ConfigureAwait(false);
+            var user = _userManager.GetUserById(userId);
+            return await _libraryManager.GetNamedView(user, name, "livetv", "zz_" + name, cancellationToken).ConfigureAwait(false);
         }
     }
 }

+ 1 - 0
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -926,6 +926,7 @@
     "LabelChannelDownloadAgeHelp": "Downloaded content older than this will be deleted. It will remain playable via internet streaming.",
     "ChannelSettingsFormHelp": "Install channels such as Trailers and Vimeo in the plugin catalog.",
     "ButtonOptions": "Options",
+    "ViewTypePlaylists": "Playlists",
     "ViewTypeMovies": "Movies",
     "ViewTypeTvShows": "TV",
     "ViewTypeGames": "Games",

+ 1 - 1
MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs

@@ -34,7 +34,7 @@ namespace MediaBrowser.Server.Implementations.Photos
             return item is T;
         }
 
-        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+        public virtual IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
             return new List<ImageType>
             {

+ 42 - 13
MediaBrowser.Server.Implementations/Photos/DynamicImageProvider.cs

@@ -20,12 +20,29 @@ namespace MediaBrowser.Server.Implementations.Photos
         private readonly IUserManager _userManager;
         private readonly ILibraryManager _libraryManager;
 
-        public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IUserManager userManager, ILibraryManager libraryManager, string[] collectionStripViewTypes)
+        public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IUserManager userManager, ILibraryManager libraryManager)
             : base(fileSystem, providerManager, applicationPaths)
         {
             _userManager = userManager;
             _libraryManager = libraryManager;
-            _collectionStripViewTypes = collectionStripViewTypes;
+        }
+
+        public override IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+        {
+            var view = (UserView)item;
+            if (IsUsingCollectionStrip(view))
+            {
+                return new List<ImageType>
+                {
+                    ImageType.Primary
+                };
+            }
+
+            return new List<ImageType>
+            {
+                ImageType.Primary,
+                ImageType.Thumb
+            };
         }
 
         protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
@@ -75,9 +92,15 @@ namespace MediaBrowser.Server.Implementations.Photos
                 return list;
             }
 
+            var isUsingCollectionStrip = IsUsingCollectionStrip(view);
+            var recursive = isUsingCollectionStrip && !new[] {CollectionType.Playlists, CollectionType.Channels}.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+
             var result = await view.GetItems(new InternalItemsQuery
             {
-                User = _userManager.GetUserById(view.UserId.Value)
+                User = _userManager.GetUserById(view.UserId.Value),
+                CollapseBoxSetItems = false,
+                Recursive = recursive,
+                ExcludeItemTypes = new[] { "UserView", "CollectionFolder"}
 
             }).ConfigureAwait(false);
 
@@ -115,13 +138,10 @@ namespace MediaBrowser.Server.Implementations.Photos
                 var audio = i as Audio;
                 if (audio != null)
                 {
-                    if (!audio.HasImage(ImageType.Primary))
+                    var album = audio.FindParent<MusicAlbum>();
+                    if (album != null && album.HasImage(ImageType.Primary))
                     {
-                        var album = audio.FindParent<MusicAlbum>();
-                        if (album != null)
-                        {
-                            return album;
-                        }
+                        return album;
                     }
                 }
 
@@ -129,7 +149,7 @@ namespace MediaBrowser.Server.Implementations.Photos
 
             }).DistinctBy(i => i.Id);
 
-            if (IsUsingCollectionStrip(view))
+            if (isUsingCollectionStrip)
             {
                 return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8);
             }
@@ -137,8 +157,6 @@ namespace MediaBrowser.Server.Implementations.Photos
             return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary)).ToList());
         }
 
-        private readonly string[] _collectionStripViewTypes = { CollectionType.Movies };
-
         public override bool Supports(IHasImages item)
         {
             var view = item as UserView;
@@ -193,7 +211,18 @@ namespace MediaBrowser.Server.Implementations.Photos
 
         private bool IsUsingCollectionStrip(UserView view)
         {
-            return _collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
+            string[] collectionStripViewTypes =
+            {
+                CollectionType.Movies,
+                CollectionType.TvShows,
+                CollectionType.Games,
+                CollectionType.Music,
+                CollectionType.BoxSets,
+                CollectionType.Playlists,
+                CollectionType.Channels
+            };
+
+            return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
         }
 
         protected override Task<Stream> CreateImageAsync(IHasImages item, List<BaseItem> itemsWithImages, ImageType imageType, int imageIndex)

+ 74 - 9
MediaBrowser.Server.Implementations/Photos/StripCollageBuilder.cs

@@ -19,31 +19,40 @@ namespace MediaBrowser.Server.Implementations.Photos
 
         public Stream BuildThumbCollage(IEnumerable<string> paths, string text, int width, int height)
         {
-            using (var wand = BuildThumbCollageWand(paths, text, width, height))
+            using (var wand = BuildThumbCollageWandWithText(paths, text, width, height))
             {
                 return DynamicImageHelpers.GetStream(wand, _appPaths);
             }
         }
 
-        private IEnumerable<string> ProjectPaths(IEnumerable<string> paths, int count)
+        private string[] ProjectPaths(IEnumerable<string> paths, int count)
         {
             var clone = paths.ToList();
             var list = new List<string>();
 
             while (list.Count < count)
             {
-                list.AddRange(clone);
+                foreach (var path in clone)
+                {
+                    list.Add(path);
+
+                    if (list.Count >= count)
+                    {
+                        break;
+                    }
+                }
             }
 
-            return list.Take(count);
+            return list.Take(count).ToArray();
         }
 
-        private MagickWand BuildThumbCollageWand(IEnumerable<string> paths, string text, int width, int height)
+        private MagickWand BuildThumbCollageWandWithText(IEnumerable<string> paths, string text, int width, int height)
         {
-            using (var wandImages = new MagickWand(ProjectPaths(paths, 8).ToArray()))
+            var inputPaths = ProjectPaths(paths, 8);
+            using (var wandImages = new MagickWand(inputPaths))
             {
                 var wand = new MagickWand(width, height);
-                wand.OpenImage("gradient:#111111-#252525");
+                wand.OpenImage("gradient:#111111-#111111");
                 using (var draw = new DrawingWand())
                 {
                     using (var fcolor = new PixelWand(ColorName.White))
@@ -86,12 +95,12 @@ namespace MediaBrowser.Server.Implementations.Photos
                             mwr.CurrentImage.FlipImage();
 
                             mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
-                            mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey56);
+                            mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey70);
 
                             using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
                             {
                                 mwg.OpenImage("gradient:black-none");
-                                var verticalSpacing = Convert.ToInt32(height * 0.00555555555555555555555555555556);
+                                var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
                                 mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing);
 
                                 wandList.AddImage(mwr);
@@ -106,6 +115,62 @@ namespace MediaBrowser.Server.Implementations.Photos
             }
         }
 
+        private MagickWand BuildThumbCollageWand(IEnumerable<string> paths, int width, int height)
+        {
+            var inputPaths = ProjectPaths(paths, 8);
+            using (var wandImages = new MagickWand(inputPaths))
+            {
+                var wand = new MagickWand(width, height);
+                wand.OpenImage("gradient:#111111-#111111");
+                using (var draw = new DrawingWand())
+                {
+                    var iSlice = Convert.ToInt32(width * .1166666667);
+                    int iTrans = Convert.ToInt32(height * .25);
+                    int iHeight = Convert.ToInt32(height * .6);
+                    var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
+
+                    foreach (var element in wandImages.ImageList)
+                    {
+                        int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
+                        element.Gravity = GravityType.CenterGravity;
+                        element.BackgroundColor = ColorName.Black;
+                        element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
+                        int ix = (int)Math.Abs((iWidth - iSlice) / 2);
+                        element.CropImage(iSlice, iHeight, ix, 0);
+
+                        element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
+                    }
+
+                    wandImages.SetFirstIterator();
+                    using (var wandList = wandImages.AppendImages())
+                    {
+                        wandList.CurrentImage.TrimImage(1);
+                        using (var mwr = wandList.CloneMagickWand())
+                        {
+                            mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
+                            mwr.CurrentImage.FlipImage();
+
+                            mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
+                            mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey60);
+
+                            using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
+                            {
+                                mwg.OpenImage("gradient:black-none");
+                                var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
+                                mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing);
+
+                                wandList.AddImage(mwr);
+                                int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
+                                wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .1));
+                            }
+                        }
+                    }
+                }
+
+                return wand;
+            }
+        }
+
         private string MontserratLightFont
         {
             get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths); }

+ 6 - 4
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -197,6 +197,7 @@ namespace MediaBrowser.Server.Startup.Common
         private ITVSeriesManager TVSeriesManager { get; set; }
         private ICollectionManager CollectionManager { get; set; }
         private IMediaSourceManager MediaSourceManager { get; set; }
+        private IPlaylistManager PlaylistManager { get; set; }
 
         private readonly StartupOptions _startupOptions;
         private readonly string _remotePackageName;
@@ -423,7 +424,7 @@ namespace MediaBrowser.Server.Startup.Common
             LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
             RegisterSingleInstance(LibraryMonitor);
 
-            ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths);
+            ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager);
             RegisterSingleInstance(ProviderManager);
 
             SeriesOrderManager = new SeriesOrderManager();
@@ -491,13 +492,13 @@ namespace MediaBrowser.Server.Startup.Common
             CollectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager"));
             RegisterSingleInstance(CollectionManager);
 
-            var playlistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager);
-            RegisterSingleInstance<IPlaylistManager>(playlistManager);
+            PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager);
+            RegisterSingleInstance<IPlaylistManager>(PlaylistManager);
 
             LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer);
             RegisterSingleInstance(LiveTvManager);
 
-            UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, playlistManager, CollectionManager, ServerConfigurationManager);
+            UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, PlaylistManager, CollectionManager, ServerConfigurationManager);
             RegisterSingleInstance(UserViewManager);
 
             var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, LocalizationManager, ChannelManager, MediaSourceManager);
@@ -702,6 +703,7 @@ namespace MediaBrowser.Server.Startup.Common
             BaseItem.LiveTvManager = LiveTvManager;
             Folder.UserViewManager = UserViewManager;
             UserView.TVSeriesManager = TVSeriesManager;
+            UserView.PlaylistManager = PlaylistManager;
             BaseItem.CollectionManager = CollectionManager;
             BaseItem.MediaSourceManager = MediaSourceManager;
         }