Răsfoiți Sursa

improve user view images

Luke Pulverenti 10 ani în urmă
părinte
comite
e33244d797
29 a modificat fișierele cu 792 adăugiri și 725 ștergeri
  1. 7 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  2. 7 0
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  3. 9 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  4. 10 1
      MediaBrowser.Controller/Entities/UserView.cs
  5. 66 81
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  6. 14 5
      MediaBrowser.Controller/Library/ILibraryManager.cs
  7. 1 1
      MediaBrowser.Controller/Library/IUserViewManager.cs
  8. 7 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  9. 7 0
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  10. 7 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  11. 4 1
      MediaBrowser.Model/Entities/CollectionType.cs
  12. 7 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  13. 8 1
      MediaBrowser.Providers/FolderImages/DefaultImageProvider.cs
  14. 33 0
      MediaBrowser.Providers/Folders/UserViewMetadataService.cs
  15. 1 1
      MediaBrowser.Providers/Manager/ProviderManager.cs
  16. 8 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  17. 92 15
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  18. 12 10
      MediaBrowser.Server.Implementations/Library/UserViewManager.cs
  19. 5 2
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  20. 10 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  21. 90 0
      MediaBrowser.Server.Implementations/Music/MusicDynamicImageProvider.cs
  22. 189 0
      MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
  23. 147 0
      MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs
  24. 10 322
      MediaBrowser.Server.Implementations/Photos/PhotoAlbumImageProvider.cs
  25. 6 285
      MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs
  26. 7 0
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  27. 7 0
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  28. 2 0
      MediaBrowser.WebDashboard/Api/PackageCreator.cs
  29. 19 0
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 7 - 0
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -172,6 +172,13 @@
     <PostBuildEvent>
     </PostBuildEvent>
   </PropertyGroup>
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 7 - 0
MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj

@@ -124,6 +124,13 @@
 xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
 )</PostBuildEvent>
   </PropertyGroup>
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 9 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -373,6 +373,15 @@ namespace MediaBrowser.Controller.Entities
             return System.IO.Path.Combine(basePath, idString.Substring(0, 2), idString);
         }
 
+        public static string GetInternalMetadataPathForId(Guid id)
+        {
+            var idString = id.ToString("N");
+
+            var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath;
+
+            return System.IO.Path.Combine(basePath, idString.Substring(0, 2), idString);
+        }
+
         /// <summary>
         /// Creates the name of the sort.
         /// </summary>

+ 10 - 1
MediaBrowser.Controller/Entities/UserView.cs

@@ -12,12 +12,21 @@ namespace MediaBrowser.Controller.Entities
         public string ViewType { get; set; }
         public Guid ParentId { get; set; }
 
+        public Guid? UserId { get; set; }
+
         public static ITVSeriesManager TVSeriesManager;
 
         public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
         {
+            var parent = this as Folder;
+
+            if (ParentId != Guid.Empty)
+            {
+                parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
+            }
+
             return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager)
-                .GetUserItems(this, ViewType, query);
+                .GetUserItems(parent, ViewType, query);
         }
 
         public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)

+ 66 - 81
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -1,5 +1,4 @@
-using System.IO;
-using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
@@ -15,6 +14,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
@@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.Entities
                         return GetResult(result);
                     }
 
-                case CollectionType.LiveTvChannels:
+                case SpecialFolder.LiveTvChannels:
                     {
                         var result = await _liveTvManager.GetInternalChannels(new LiveTvChannelQuery
                         {
@@ -76,7 +76,7 @@ namespace MediaBrowser.Controller.Entities
                         return GetResult(result);
                     }
 
-                case CollectionType.LiveTvNowPlaying:
+                case SpecialFolder.LiveTvNowPlaying:
                     {
                         var result = await _liveTvManager.GetRecommendedProgramsInternal(new RecommendedProgramQuery
                         {
@@ -89,7 +89,7 @@ namespace MediaBrowser.Controller.Entities
                         return GetResult(result);
                     }
 
-                case CollectionType.LiveTvRecordingGroups:
+                case SpecialFolder.LiveTvRecordingGroups:
                     {
                         var result = await _liveTvManager.GetInternalRecordings(new RecordingQuery
                         {
@@ -128,85 +128,85 @@ namespace MediaBrowser.Controller.Entities
                 case CollectionType.Movies:
                     return await GetMovieFolders(parent, user, query).ConfigureAwait(false);
 
-                case CollectionType.GameGenres:
+                case SpecialFolder.GameGenres:
                     return GetGameGenres(parent, user, query);
 
-                case CollectionType.GameSystems:
+                case SpecialFolder.GameSystems:
                     return GetGameSystems(parent, user, query);
 
-                case CollectionType.LatestGames:
+                case SpecialFolder.LatestGames:
                     return GetLatestGames(parent, user, query);
 
-                case CollectionType.RecentlyPlayedGames:
+                case SpecialFolder.RecentlyPlayedGames:
                     return GetRecentlyPlayedGames(parent, user, query);
 
-                case CollectionType.GameFavorites:
+                case SpecialFolder.GameFavorites:
                     return GetFavoriteGames(parent, user, query);
 
-                case CollectionType.TvShowSeries:
+                case SpecialFolder.TvShowSeries:
                     return GetTvSeries(parent, user, query);
 
-                case CollectionType.TvGenres:
+                case SpecialFolder.TvGenres:
                     return GetTvGenres(parent, user, query);
 
-                case CollectionType.TvResume:
+                case SpecialFolder.TvResume:
                     return GetTvResume(parent, user, query);
 
-                case CollectionType.TvNextUp:
+                case SpecialFolder.TvNextUp:
                     return GetTvNextUp(parent, query);
 
-                case CollectionType.TvLatest:
+                case SpecialFolder.TvLatest:
                     return GetTvLatest(parent, user, query);
 
-                case CollectionType.MovieFavorites:
+                case SpecialFolder.MovieFavorites:
                     return GetFavoriteMovies(parent, user, query);
 
-                case CollectionType.MovieLatest:
+                case SpecialFolder.MovieLatest:
                     return GetMovieLatest(parent, user, query);
 
-                case CollectionType.MovieGenres:
+                case SpecialFolder.MovieGenres:
                     return GetMovieGenres(parent, user, query);
 
-                case CollectionType.MovieResume:
+                case SpecialFolder.MovieResume:
                     return GetMovieResume(parent, user, query);
 
-                case CollectionType.MovieMovies:
+                case SpecialFolder.MovieMovies:
                     return GetMovieMovies(parent, user, query);
 
-                case CollectionType.MovieCollections:
+                case SpecialFolder.MovieCollections:
                     return GetMovieCollections(parent, user, query);
 
-                case CollectionType.MusicLatest:
+                case SpecialFolder.MusicLatest:
                     return GetMusicLatest(parent, user, query);
 
-                case CollectionType.MusicAlbums:
+                case SpecialFolder.MusicAlbums:
                     return GetMusicAlbums(parent, user, query);
 
-                case CollectionType.MusicAlbumArtists:
+                case SpecialFolder.MusicAlbumArtists:
                     return GetMusicAlbumArtists(parent, user, query);
 
-                case CollectionType.MusicArtists:
+                case SpecialFolder.MusicArtists:
                     return GetMusicArtists(parent, user, query);
 
-                case CollectionType.MusicSongs:
+                case SpecialFolder.MusicSongs:
                     return GetMusicSongs(parent, user, query);
 
-                case CollectionType.TvFavoriteEpisodes:
+                case SpecialFolder.TvFavoriteEpisodes:
                     return GetFavoriteEpisodes(parent, user, query);
 
-                case CollectionType.TvFavoriteSeries:
+                case SpecialFolder.TvFavoriteSeries:
                     return GetFavoriteSeries(parent, user, query);
 
-                case CollectionType.MusicFavorites:
+                case SpecialFolder.MusicFavorites:
                     return await GetMusicFavorites(parent, user, query).ConfigureAwait(false);
 
-                case CollectionType.MusicFavoriteAlbums:
+                case SpecialFolder.MusicFavoriteAlbums:
                     return GetFavoriteAlbums(parent, user, query);
 
-                case CollectionType.MusicFavoriteArtists:
+                case SpecialFolder.MusicFavoriteArtists:
                     return GetFavoriteArtists(parent, user, query);
 
-                case CollectionType.MusicFavoriteSongs:
+                case SpecialFolder.MusicFavoriteSongs:
                     return GetFavoriteSongs(parent, user, query);
 
                 default:
@@ -228,15 +228,13 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            var category = "music";
-
-            list.Add(await GetUserView(category, CollectionType.MusicLatest, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MusicAlbums, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MusicAlbumArtists, user, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MusicSongs, user, "3", parent).ConfigureAwait(false));
-            //list.Add(await GetUserView(CollectionType.MusicArtists, user, "3", parent).ConfigureAwait(false));
-            //list.Add(await GetUserView(CollectionType.MusicGenres, user, "5", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MusicFavorites, user, "6", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicLatest, user, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicAlbums, user, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicAlbumArtists, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicSongs, user, "3", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(SpecialFolder.MusicArtists, user, "3", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(SpecialFolder.MusicGenres, user, "5", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicFavorites, user, "6", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -245,11 +243,9 @@ namespace MediaBrowser.Controller.Entities
         {
             var list = new List<BaseItem>();
 
-            var category = "music";
-
-            list.Add(await GetUserView(category, CollectionType.MusicFavoriteAlbums, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MusicFavoriteArtists, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MusicFavoriteSongs, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicFavoriteAlbums, user, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicFavoriteArtists, user, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicFavoriteSongs, user, "2", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -353,13 +349,11 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            var category = "movies";
-
-            list.Add(await GetUserView(category, CollectionType.MovieResume, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MovieLatest, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MovieMovies, user, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MovieCollections, user, "3", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.MovieFavorites, user, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieResume, user, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieLatest, user, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieMovies, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieCollections, user, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieFavorites, user, "4", parent).ConfigureAwait(false));
             //list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
@@ -461,15 +455,13 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            var category = "tvshows";
-
-            list.Add(await GetUserView(category, CollectionType.TvResume, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.TvNextUp, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.TvLatest, user, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.TvShowSeries, user, "3", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.TvFavoriteSeries, user, "4", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.TvFavoriteEpisodes, user, "5", parent).ConfigureAwait(false));
-            //list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvResume, user, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvNextUp, user, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvLatest, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvShowSeries, user, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvFavoriteSeries, user, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvFavoriteEpisodes, user, "5", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(SpecialFolder.TvGenres, user, "5", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -483,13 +475,11 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            var category = "games";
-
-            list.Add(await GetUserView(category, CollectionType.LatestGames, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.GameFavorites, user, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(category, CollectionType.GameSystems, user, "3", parent).ConfigureAwait(false));
-            //list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.LatestGames, user, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.GameFavorites, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.GameSystems, user, "3", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(SpecialFolder.GameGenres, user, "4", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -1491,25 +1481,20 @@ namespace MediaBrowser.Controller.Entities
         {
             var list = new List<BaseItem>();
 
-            list.Add(await _userViewManager.GetUserView("livetv", CollectionType.LiveTvNowPlaying, user, "0", CancellationToken.None).ConfigureAwait(false));
-            list.Add(await _userViewManager.GetUserView("livetv", CollectionType.LiveTvChannels, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
-            list.Add(await _userViewManager.GetUserView("livetv", CollectionType.LiveTvRecordingGroups, user, string.Empty, CancellationToken.None).ConfigureAwait(false));
+            var parent = user.RootFolder;
+
+            list.Add(await GetUserView(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));
 
             return list;
         }
 
-        private async Task<UserView> GetUserView(string category, string type, User user, string sortName, Folder parent)
+        private async Task<UserView> GetUserView(string type, User user, string sortName, Folder parent)
         {
-            var view = await _userViewManager.GetUserView(category, type, user, sortName, CancellationToken.None)
+            var view = await _userViewManager.GetUserView(parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
                         .ConfigureAwait(false);
 
-            if (parent.Id != view.ParentId)
-            {
-                view.ParentId = parent.Id;
-                await view.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None)
-                        .ConfigureAwait(false);
-            }
-
             return view;
         }
 

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

@@ -325,15 +325,21 @@ namespace MediaBrowser.Controller.Library
         IEnumerable<BaseItem> ReplaceVideosWithPrimaryVersions(IEnumerable<BaseItem> items);
 
         /// <summary>
-        /// Gets the named folder.
+        /// Gets the special folder.
         /// </summary>
+        /// <param name="user">The user.</param>
         /// <param name="name">The name.</param>
-        /// <param name="category">The category.</param>
+        /// <param name="parentId">The parent identifier.</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{Folder}.</returns>
-        Task<UserView> GetNamedView(string name, string category, string viewType, string sortName, CancellationToken cancellationToken);
+        /// <returns>Task&lt;UserView&gt;.</returns>
+        Task<UserView> GetSpecialFolder(User user,
+            string name,
+            string parentId,
+            string viewType, 
+            string sortName, 
+            CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the named view.
@@ -343,6 +349,9 @@ 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> GetNamedView(string name, string viewType, string sortName, CancellationToken cancellationToken);
+        Task<UserView> GetNamedView(string name, 
+            string viewType, 
+            string sortName, 
+            CancellationToken cancellationToken);
     }
 }

+ 1 - 1
MediaBrowser.Controller/Library/IUserViewManager.cs

@@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Library
     {
         Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken);
 
-        Task<UserView> GetUserView(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);
     }

+ 7 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -369,6 +369,13 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
     <PreBuildEvent>
     </PreBuildEvent>
   </PropertyGroup>
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 7 - 0
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -91,6 +91,13 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 7 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -1124,6 +1124,13 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\portable\" /y /d /r /i
 )</PostBuildEvent>
   </PropertyGroup>
   <Import Project="Fody.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 4 - 1
MediaBrowser.Model/Entities/CollectionType.cs

@@ -25,7 +25,10 @@
         public const string LiveTv = "livetv";
         public const string Playlists = "playlists";
         public const string Folders = "folders";
+    }
 
+    public static class SpecialFolder
+    {
         public const string LiveTvNowPlaying = "LiveTvNowPlaying";
         public const string LiveTvChannels = "LiveTvChannels";
         public const string LiveTvRecordingGroups = "LiveTvRecordingGroups";
@@ -44,7 +47,7 @@
         public const string MovieCollections = "MovieCollections";
         public const string MovieFavorites = "MovieFavorites";
         public const string MovieGenres = "MovieGenres";
-        
+
         public const string LatestGames = "LatestGames";
         public const string RecentlyPlayedGames = "RecentlyPlayedGames";
         public const string GameSystems = "GameSystems";

+ 7 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -428,6 +428,13 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
 )</PostBuildEvent>
   </PropertyGroup>
   <Import Project="Fody.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 8 - 1
MediaBrowser.Providers/FolderImages/DefaultImageProvider.cs

@@ -112,7 +112,14 @@ namespace MediaBrowser.Providers.FolderImages
 
         public bool Supports(IHasImages item)
         {
-            return item is UserView || item is ICollectionFolder;
+            var view = item as UserView;
+
+            if (view != null)
+            {
+                return !view.UserId.HasValue;
+            }
+            
+            return item is ICollectionFolder;
         }
 
         public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)

+ 33 - 0
MediaBrowser.Providers/Folders/UserViewMetadataService.cs

@@ -0,0 +1,33 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Providers.Folders
+{
+    public class UserViewMetadataService : MetadataService<UserView, ItemLookupInfo>
+    {
+        public UserViewMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        {
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(UserView source, UserView target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -468,7 +468,7 @@ namespace MediaBrowser.Providers.Manager
             // Give it a dummy path just so that it looks like a file system item
             var dummy = new T()
             {
-                Path = "C:\\",
+                Path = BaseItem.GetInternalMetadataPathForId(Guid.NewGuid()),
 
                 // Dummy this up to fool the local trailer check
                 Parent = new Folder()

+ 8 - 0
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -85,6 +85,7 @@
     <Compile Include="FolderImages\DefaultImageProvider.cs" />
     <Compile Include="Folders\FolderMetadataService.cs" />
     <Compile Include="Channels\AudioChannelItemMetadataService.cs" />
+    <Compile Include="Folders\UserViewMetadataService.cs" />
     <Compile Include="GameGenres\GameGenreMetadataService.cs" />
     <Compile Include="Channels\VideoChannelItemMetadataService.cs" />
     <Compile Include="Games\GameMetadataService.cs" />
@@ -216,6 +217,13 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 92 - 15
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -530,8 +530,8 @@ namespace MediaBrowser.Server.Implementations.Library
             return item;
         }
 
-        public BaseItem ResolvePath(FileSystemInfo fileInfo, 
-            Folder parent = null, 
+        public BaseItem ResolvePath(FileSystemInfo fileInfo,
+            Folder parent = null,
             string collectionType = null)
         {
             return ResolvePath(fileInfo, new DirectoryService(_logger), parent, collectionType);
@@ -1190,7 +1190,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
             return item;
         }
-        
+
         /// <summary>
         /// Gets the intros.
         /// </summary>
@@ -1508,27 +1508,22 @@ namespace MediaBrowser.Server.Implementations.Library
             return collectionTypes.Count == 1 ? collectionTypes[0] : null;
         }
 
-        public Task<UserView> GetNamedView(string name, string type, string sortName, CancellationToken cancellationToken)
-        {
-            return GetNamedView(name, null, type, sortName, cancellationToken);
-        }
-
-        public async Task<UserView> GetNamedView(string name, string category, string type, string sortName, CancellationToken cancellationToken)
+        public async Task<UserView> GetNamedView(string name,
+            string type,
+            string sortName,
+            CancellationToken cancellationToken)
         {
             var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath,
                 "views");
 
-            if (!string.IsNullOrWhiteSpace(category))
-            {
-                path = Path.Combine(path, _fileSystem.GetValidFilename(category));
-            }
-
             path = Path.Combine(path, _fileSystem.GetValidFilename(type));
 
             var id = (path + "_namedview_" + name).GetMBId(typeof(UserView));
 
             var item = GetItemById(id) as UserView;
 
+            var refresh = false;
+
             if (item == null ||
                 !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
             {
@@ -1546,7 +1541,89 @@ namespace MediaBrowser.Server.Implementations.Library
 
                 await CreateItem(item, cancellationToken).ConfigureAwait(false);
 
-                await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+                refresh = true;
+            }
+
+            if (!refresh && item != null)
+            {
+                refresh = (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 24;
+            }
+
+            if (refresh)
+            {
+                await item.RefreshMetadata(new MetadataRefreshOptions
+                {
+                    ForceSave = true
+
+                }, cancellationToken).ConfigureAwait(false);
+            }
+
+            return item;
+        }
+
+        public async Task<UserView> GetSpecialFolder(User user,
+            string name,
+            string parentId,
+            string viewType,
+            string sortName,
+            CancellationToken cancellationToken)
+        {
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentNullException("name");
+            }
+
+            if (string.IsNullOrWhiteSpace(parentId))
+            {
+                throw new ArgumentNullException("parentId");
+            }
+
+            if (string.IsNullOrWhiteSpace(viewType))
+            {
+                throw new ArgumentNullException("viewType");
+            }
+
+            var id = ("7_namedview_" + name + user.Id.ToString("N") + parentId).GetMBId(typeof(UserView));
+
+            var path = BaseItem.GetInternalMetadataPathForId(id);
+
+            var item = GetItemById(id) as UserView;
+
+            var refresh = false;
+
+            if (item == null)
+            {
+                Directory.CreateDirectory(path);
+
+                item = new UserView
+                {
+                    Path = path,
+                    Id = id,
+                    DateCreated = DateTime.UtcNow,
+                    Name = name,
+                    ViewType = viewType,
+                    ForcedSortName = sortName,
+                    UserId = user.Id,
+                    ParentId = new Guid(parentId)
+                };
+
+                await CreateItem(item, cancellationToken).ConfigureAwait(false);
+
+                refresh = true;
+            }
+
+            if (!refresh && item != null)
+            {
+                refresh = (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 24;
+            }
+
+            if (refresh)
+            {
+                await item.RefreshMetadata(new MetadataRefreshOptions
+                {
+                    ForceSave = true
+
+                }, cancellationToken).ConfigureAwait(false);
             }
 
             return item;

+ 12 - 10
MediaBrowser.Server.Implementations/Library/UserViewManager.cs

@@ -68,24 +68,24 @@ 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, user, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.TvShows, string.Empty, 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, user, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.Music, string.Empty, 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, user, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.Movies, string.Empty, cancellationToken).ConfigureAwait(false));
             }
 
             if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Games, StringComparison.OrdinalIgnoreCase)))
             {
-                list.Add(await GetUserView(CollectionType.Games, user, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.Games, string.Empty, cancellationToken).ConfigureAwait(false));
             }
 
             if (user.Configuration.DisplayCollectionsView &&
@@ -93,7 +93,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 .Except(standaloneFolders)
                 .SelectMany(i => i.GetRecursiveChildren(user, false)).OfType<BoxSet>().Any())
             {
-                list.Add(await GetUserView(CollectionType.BoxSets, user, string.Empty, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.BoxSets, string.Empty, cancellationToken).ConfigureAwait(false));
             }
 
             if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)))
@@ -103,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
             if (user.Configuration.DisplayFoldersView)
             {
-                list.Add(await GetUserView(CollectionType.Folders, user, "zz_" + CollectionType.Folders, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(CollectionType.Folders, "zz_" + CollectionType.Folders, cancellationToken).ConfigureAwait(false));
             }
 
             if (query.IncludeExternalContent)
@@ -148,16 +148,18 @@ namespace MediaBrowser.Server.Implementations.Library
                 .ThenBy(i => i.SortName);
         }
 
-        public Task<UserView> GetUserView(string category, string type, User user, string sortName, CancellationToken cancellationToken)
+        public Task<UserView> GetUserView(string parentId, string type, User user, string sortName, CancellationToken cancellationToken)
         {
             var name = _localizationManager.GetLocalizedString("ViewType" + type);
 
-            return _libraryManager.GetNamedView(name, category, type, sortName, cancellationToken);
+            return _libraryManager.GetSpecialFolder(user, name, parentId, type, sortName, cancellationToken);
         }
 
-        public Task<UserView> GetUserView(string type, User user, string sortName, CancellationToken cancellationToken)
+        public Task<UserView> GetUserView(string type, string sortName, CancellationToken cancellationToken)
         {
-            return GetUserView(null, type, user, sortName, cancellationToken);
+            var name = _localizationManager.GetLocalizedString("ViewType" + type);
+
+            return _libraryManager.GetNamedView(name, type, sortName, cancellationToken);
         }
     }
 }

+ 5 - 2
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -830,7 +830,7 @@
     "HeaderWelcomeToMediaBrowserWebClient": "Welcome to the Media Browser Web Client",
     "ButtonDismiss": "Dismiss",
     "ButtonTakeTheTour": "Take the tour",
-    "ButtonEditOtherUserPreferences": "Edit this user's profile and personal preferences.",
+    "ButtonEditOtherUserPreferences": "Edit this user's profile, password and personal preferences.",
     "LabelChannelStreamQuality": "Preferred internet stream quality:",
     "LabelChannelStreamQualityHelp": "In a low bandwidth environment, limiting quality can help ensure a smooth streaming experience.",
     "OptionBestAvailableStreamQuality": "Best available",
@@ -1260,5 +1260,8 @@
     "OptionDisableUserPreferences": "Disable access to user preferences",
     "OptionDisableUserPreferencesHelp": "If enabled, only administrators will be able to configure user profile images, passwords, and language preferences.",
     "HeaderSelectServer": "Select Server",
-    "MessageNoServersAvailableToConnect": "No servers are available to connect to. If you've been invited to share a server, make sure to confirm it by clicking the link in the email."
+    "MessageNoServersAvailableToConnect": "No servers are available to connect to. If you've been invited to share a server, make sure to confirm it by clicking the link in the email.",
+    "TitleNewUser": "New User",
+    "ButtonConfigurePassword": "Configure Password",
+    "HeaderDashboardUserPassword": "User passwords are managed within each user's personal profile settings."
 }

+ 10 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -226,6 +226,7 @@
     <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
     <Compile Include="Localization\LocalizationManager.cs" />
     <Compile Include="MediaEncoder\EncodingManager.cs" />
+    <Compile Include="Music\MusicDynamicImageProvider.cs" />
     <Compile Include="News\NewsEntryPoint.cs" />
     <Compile Include="News\NewsService.cs" />
     <Compile Include="Notifications\CoreNotificationTypes.cs" />
@@ -240,6 +241,8 @@
     <Compile Include="Persistence\SqliteProviderInfoRepository.cs" />
     <Compile Include="Persistence\SqliteShrinkMemoryTimer.cs" />
     <Compile Include="Persistence\TypeMapper.cs" />
+    <Compile Include="Photos\BaseDynamicImageProvider.cs" />
+    <Compile Include="Photos\DynamicImageHelpers.cs" />
     <Compile Include="Playlists\ManualPlaylistsFolder.cs" />
     <Compile Include="Photos\PhotoAlbumImageProvider.cs" />
     <Compile Include="Playlists\PlaylistImageProvider.cs" />
@@ -515,6 +518,13 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 90 - 0
MediaBrowser.Server.Implementations/Music/MusicDynamicImageProvider.cs

@@ -0,0 +1,90 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Server.Implementations.Photos;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Music
+{
+    public class MusicDynamicImageProvider : BaseDynamicImageProvider<UserView>, ICustomMetadataProvider<UserView>
+    {
+        private readonly IUserManager _userManager;
+
+        public MusicDynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IUserManager userManager)
+            : base(fileSystem, providerManager)
+        {
+            _userManager = userManager;
+        }
+
+        protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
+        {
+            var view = (UserView)item;
+
+            if (!view.UserId.HasValue)
+            {
+                return new List<BaseItem>();
+            }
+
+            var result = await view.GetItems(new InternalItemsQuery
+            {
+                User = _userManager.GetUserById(view.UserId.Value)
+
+            }).ConfigureAwait(false);
+
+            return GetFinalItems(result.Items.Where(i => i.HasImage(ImageType.Primary)).ToList());
+        }
+
+        protected override bool Supports(IHasImages item)
+        {
+            var view = item as UserView;
+
+            if (view != null && view.UserId.HasValue)
+            {
+                var supported = new[]
+                {
+                    SpecialFolder.TvFavoriteEpisodes,
+                    SpecialFolder.TvFavoriteSeries,
+                    SpecialFolder.TvGenres,
+                    SpecialFolder.TvLatest,
+                    SpecialFolder.TvNextUp,
+                    SpecialFolder.TvResume,
+                    SpecialFolder.TvShowSeries,
+
+                    SpecialFolder.MovieCollections,
+                    SpecialFolder.MovieFavorites,
+                    SpecialFolder.MovieGenres,
+                    SpecialFolder.MovieLatest,
+                    SpecialFolder.MovieMovies,
+                    SpecialFolder.MovieResume,
+
+                    SpecialFolder.GameFavorites,
+                    SpecialFolder.GameGenres,
+                    SpecialFolder.GameSystems,
+                    SpecialFolder.LatestGames,
+                    SpecialFolder.RecentlyPlayedGames,
+
+                    SpecialFolder.MusicArtists,
+                    SpecialFolder.MusicAlbumArtists,
+                    SpecialFolder.MusicAlbums,
+                    SpecialFolder.MusicGenres,
+                    SpecialFolder.MusicLatest,
+                    SpecialFolder.MusicSongs,
+                    SpecialFolder.MusicFavorites,
+                    SpecialFolder.MusicFavoriteArtists,
+                    SpecialFolder.MusicFavoriteAlbums,
+                    SpecialFolder.MusicFavoriteSongs
+                };
+
+                return supported.Contains(view.ViewType, StringComparer.OrdinalIgnoreCase) &&
+                    _userManager.GetUserById(view.UserId.Value) != null;
+            }
+
+            return false;
+        }
+    }
+}

+ 189 - 0
MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs

@@ -0,0 +1,189 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Photos
+{
+    public abstract class BaseDynamicImageProvider<T> : IHasChangeMonitor
+        where T : IHasImages
+    {
+        protected IFileSystem FileSystem { get; private set; }
+        protected IProviderManager ProviderManager { get; private set; }
+
+        protected BaseDynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager)
+        {
+            ProviderManager = providerManager;
+            FileSystem = fileSystem;
+        }
+
+        public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
+        {
+            if (!Supports(item))
+            {
+                return ItemUpdateType.None;
+            }
+
+            var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false);
+            var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false);
+
+            return primaryResult | thumbResult;
+        }
+
+        protected virtual bool Supports(IHasImages item)
+        {
+            return true;
+        }
+
+        protected abstract Task<List<BaseItem>> GetItemsWithImages(IHasImages item);
+
+        private const string Version = "3";
+        protected  string GetConfigurationCacheKey(List<BaseItem> items)
+        {
+            return (Version + "_" + string.Join(",", items.Select(i => i.Id.ToString("N")).ToArray())).GetMD5().ToString("N");
+        }
+
+        protected async Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
+        {
+            var items = await GetItemsWithImages(item).ConfigureAwait(false);
+            var cacheKey = GetConfigurationCacheKey(items);
+
+            if (!HasChanged(item, imageType, cacheKey))
+            {
+                return ItemUpdateType.None;
+            }
+
+            return await FetchAsyncInternal(item, items, imageType, cacheKey, options, cancellationToken).ConfigureAwait(false);
+        }
+
+        protected async Task<ItemUpdateType> FetchAsyncInternal(IHasImages item, 
+            List<BaseItem> itemsWithImages,
+            ImageType imageType, 
+            string cacheKey, 
+            MetadataRefreshOptions options, 
+            CancellationToken cancellationToken)
+        {
+            var img = await CreateImageAsync(item, itemsWithImages, imageType, 0).ConfigureAwait(false);
+
+            if (img == null)
+            {
+                return ItemUpdateType.None;
+            }
+
+            using (var ms = new MemoryStream())
+            {
+                img.Save(ms, ImageFormat.Png);
+
+                ms.Position = 0;
+
+                await ProviderManager.SaveImage(item, ms, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false);
+            }
+
+            return ItemUpdateType.ImageUpdate;
+        }
+
+        protected Task<Image> GetThumbCollage(List<BaseItem> items)
+        {
+            return DynamicImageHelpers.GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList(),
+                FileSystem,
+                1600,
+                900);
+        }
+
+        protected Task<Image> GetSquareCollage(List<BaseItem> items)
+        {
+            return DynamicImageHelpers.GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList(),
+                FileSystem,
+                800);
+        }
+
+        public string Name
+        {
+            get { return "Dynamic Image Provider"; }
+        }
+
+        public async Task<Image> CreateImageAsync(IHasImages item, 
+            List<BaseItem> itemsWithImages,
+            ImageType imageType, 
+            int imageIndex)
+        {
+            if (itemsWithImages.Count == 0)
+            {
+                return null;
+            }
+
+            return imageType == ImageType.Thumb ?
+                await GetThumbCollage(itemsWithImages).ConfigureAwait(false) :
+                await GetSquareCollage(itemsWithImages).ConfigureAwait(false);
+        }
+
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
+        {
+            if (!Supports(item))
+            {
+                return false;
+            }
+
+            var items = GetItemsWithImages(item).Result;
+            var cacheKey = GetConfigurationCacheKey(items);
+
+            return HasChanged(item, ImageType.Primary, cacheKey) || HasChanged(item, ImageType.Thumb, cacheKey);
+        }
+
+        protected bool HasChanged(IHasImages item, ImageType type, string cacheKey)
+        {
+            var image = item.GetImageInfo(type, 0);
+
+            if (image != null)
+            {
+                if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
+                {
+                    return false;
+                }
+
+                var currentPathCacheKey = (Path.GetFileNameWithoutExtension(image.Path) ?? string.Empty).Split('_').LastOrDefault();
+
+                if (string.Equals(cacheKey, currentPathCacheKey, StringComparison.OrdinalIgnoreCase))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        protected List<BaseItem> GetFinalItems(List<BaseItem> items)
+        {
+            // Rotate the images no more than once per week
+            var random = new Random(GetWeekOfYear()).Next();
+
+            return items
+                .OrderBy(i => random - items.IndexOf(i))
+                .Take(4)
+                .OrderBy(i => i.Name)
+                .ToList();
+        }
+
+        private int GetWeekOfYear()
+        {
+            var usCulture = new CultureInfo("en-US");
+            var weekNo = usCulture.Calendar.GetWeekOfYear(
+                            DateTime.Now,
+                            usCulture.DateTimeFormat.CalendarWeekRule,
+                            usCulture.DateTimeFormat.FirstDayOfWeek);
+
+            return weekNo;
+        }
+    }
+}

+ 147 - 0
MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs

@@ -0,0 +1,147 @@
+using MediaBrowser.Common.IO;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Photos
+{
+    public static class DynamicImageHelpers
+    {
+        public static async Task<Image> GetThumbCollage(List<string> files,
+            IFileSystem fileSystem,
+            int width,
+            int height)
+        {
+            if (files.Count < 3)
+            {
+                return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
+            }
+
+            const int rows = 1;
+            const int cols = 3;
+
+             int cellWidth = 2 * (width / 3);
+             int cellHeight = height;
+            var index = 0;
+
+            var img = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
+
+            using (var graphics = Graphics.FromImage(img))
+            {
+                graphics.CompositingQuality = CompositingQuality.HighQuality;
+                graphics.SmoothingMode = SmoothingMode.HighQuality;
+                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
+                graphics.CompositingMode = CompositingMode.SourceCopy;
+
+                for (var row = 0; row < rows; row++)
+                {
+                    for (var col = 0; col < cols; col++)
+                    {
+                        var x = col * (cellWidth / 2);
+                        var y = row * cellHeight;
+
+                        if (files.Count > index)
+                        {
+                            using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
+                            {
+                                using (var memoryStream = new MemoryStream())
+                                {
+                                    await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+                                    memoryStream.Position = 0;
+
+                                    using (var imgtemp = Image.FromStream(memoryStream, true, false))
+                                    {
+                                        graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
+                                    }
+                                }
+                            }
+                        }
+
+                        index++;
+                    }
+                }
+            }
+
+            return img;
+        }
+        
+        public static async Task<Image> GetSquareCollage(List<string> files,
+            IFileSystem fileSystem,
+            int size)
+        {
+            if (files.Count < 4)
+            {
+                return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
+            }
+
+            const int rows = 2;
+            const int cols = 2;
+
+            int singleSize = size / 2;
+            var index = 0;
+
+            var img = new Bitmap(size, size, PixelFormat.Format32bppPArgb);
+
+            using (var graphics = Graphics.FromImage(img))
+            {
+                graphics.CompositingQuality = CompositingQuality.HighQuality;
+                graphics.SmoothingMode = SmoothingMode.HighQuality;
+                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
+                graphics.CompositingMode = CompositingMode.SourceCopy;
+
+                for (var row = 0; row < rows; row++)
+                {
+                    for (var col = 0; col < cols; col++)
+                    {
+                        var x = col * singleSize;
+                        var y = row * singleSize;
+
+                        using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
+                        {
+                            using (var memoryStream = new MemoryStream())
+                            {
+                                await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+                                memoryStream.Position = 0;
+
+                                using (var imgtemp = Image.FromStream(memoryStream, true, false))
+                                {
+                                    graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
+                                }
+                            }
+                        }
+
+                        index++;
+                    }
+                }
+            }
+
+            return img;
+        }
+
+        private static Task<Image> GetSingleImage(List<string> files, IFileSystem fileSystem)
+        {
+            return GetImage(files[0], fileSystem);
+        }
+
+        private static async Task<Image> GetImage(string file, IFileSystem fileSystem)
+        {
+            using (var fileStream = fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true))
+            {
+                var memoryStream = new MemoryStream();
+
+                await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+                memoryStream.Position = 0;
+
+                return Image.FromStream(memoryStream, true, false);
+            }
+        }
+    }
+}

+ 10 - 322
MediaBrowser.Server.Implementations/Photos/PhotoAlbumImageProvider.cs

@@ -1,337 +1,25 @@
-using System;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
-using System.IO;
 using System.Linq;
-using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Playlists;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MoreLinq;
 
 namespace MediaBrowser.Server.Implementations.Photos
 {
-    public class PhotoAlbumImageProvider : ICustomMetadataProvider<PhotoAlbum>, IHasChangeMonitor
+    public class PhotoAlbumImageProvider : BaseDynamicImageProvider<PhotoAlbum>, ICustomMetadataProvider<PhotoAlbum>
     {
-        private readonly IFileSystem _fileSystem;
-        private readonly IProviderManager _provider;
-
-        public PhotoAlbumImageProvider(IFileSystem fileSystem, IProviderManager provider)
-        {
-            _fileSystem = fileSystem;
-            _provider = provider;
-        }
-
-        public async Task<ItemUpdateType> FetchAsync(PhotoAlbum item, MetadataRefreshOptions options, CancellationToken cancellationToken)
-        {
-            var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false);
-            var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false);
-
-            return primaryResult | thumbResult;
-        }
-
-        private Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
-        {
-            var items = GetItemsWithImages(item);
-            var cacheKey = GetConfigurationCacheKey(items);
-
-            if (!HasChanged(item, imageType, cacheKey))
-            {
-                return Task.FromResult(ItemUpdateType.None);
-            }
-
-            return FetchAsyncInternal(item, imageType, cacheKey, options, cancellationToken);
-        }
-
-        private async Task<ItemUpdateType> FetchAsyncInternal(IHasImages item, ImageType imageType, string cacheKey, MetadataRefreshOptions options, CancellationToken cancellationToken)
-        {
-            var img = await CreateImageAsync(item, imageType, 0).ConfigureAwait(false);
-
-            if (img == null)
-            {
-                return ItemUpdateType.None;
-            }
-
-            using (var ms = new MemoryStream())
-            {
-                img.Save(ms, ImageFormat.Png);
-
-                ms.Position = 0;
-
-                await _provider.SaveImage(item, ms, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false);
-            }
-
-            return ItemUpdateType.ImageUpdate;
-        }
-
-        private bool HasChanged(IHasImages item, ImageType type, string cacheKey)
-        {
-            var image = item.GetImageInfo(type, 0);
-
-            if (image != null)
-            {
-                if (!_fileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
-                {
-                    return false;
-                }
-
-                var currentPathCacheKey = (Path.GetFileNameWithoutExtension(image.Path) ?? string.Empty).Split('_').LastOrDefault();
-
-                if (string.Equals(cacheKey, currentPathCacheKey, StringComparison.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        private const string Version = "3";
-
-        public string GetConfigurationCacheKey(List<BaseItem> items)
-        {
-            return (Version + "_" + string.Join(",", items.Select(i => i.Id.ToString("N")).ToArray())).GetMD5().ToString("N");
-        }
-
-        private List<BaseItem> GetItemsWithImages(IHasImages item)
-        {
-            var photoAlbum = item as PhotoAlbum;
-            if (photoAlbum != null)
-            {
-                return GetFinalItems(photoAlbum.RecursiveChildren.Where(i => i is Photo).ToList());
-            }
-
-            var playlist = (Playlist)item;
-
-            var items = playlist.GetManageableItems()
-                .Select(i =>
-                {
-                    var subItem = i.Item2;
-
-                    var episode = subItem as Episode;
-
-                    if (episode != null)
-                    {
-                        var series = episode.Series;
-                        if (series != null && series.HasImage(ImageType.Primary))
-                        {
-                            return series;
-                        }
-                    }
-
-                    if (subItem.HasImage(ImageType.Primary))
-                    {
-                        return subItem;
-                    }
-
-                    var parent = subItem.Parent;
-
-                    if (parent != null && parent.HasImage(ImageType.Primary))
-                    {
-                        if (parent is MusicAlbum)
-                        {
-                            return parent;
-                        }
-                    }
-
-                    return null;
-                })
-                .Where(i => i != null)
-                .DistinctBy(i => i.Id)
-                .ToList();
-
-            return GetFinalItems(items);
-        }
-
-        private List<BaseItem> GetFinalItems(List<BaseItem> items)
-        {
-            // Rotate the images no more than once per day
-            var random = new Random(DateTime.Now.DayOfYear).Next();
-
-            return items
-                .OrderBy(i => random - items.IndexOf(i))
-                .Take(4)
-                .OrderBy(i => i.Name)
-                .ToList();
-        }
-
-        public async Task<Image> CreateImageAsync(IHasImages item, ImageType imageType, int imageIndex)
-        {
-            var items = GetItemsWithImages(item);
-
-            if (items.Count == 0)
-            {
-                return null;
-            }
-
-            return imageType == ImageType.Thumb ?
-                await GetThumbCollage(items).ConfigureAwait(false) :
-                await GetSquareCollage(items).ConfigureAwait(false);
-        }
-
-        private Task<Image> GetThumbCollage(List<BaseItem> items)
-        {
-            return GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList());
-        }
-
-        private Task<Image> GetSquareCollage(List<BaseItem> items)
-        {
-            return GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList());
-        }
-
-        private async Task<Image> GetThumbCollage(List<string> files)
-        {
-            if (files.Count < 3)
-            {
-                return await GetSingleImage(files).ConfigureAwait(false);
-            }
-
-            const int rows = 1;
-            const int cols = 3;
-
-            const int cellWidth = 2 * (ThumbImageWidth / 3);
-            const int cellHeight = ThumbImageHeight;
-            var index = 0;
-
-            var img = new Bitmap(ThumbImageWidth, ThumbImageHeight, PixelFormat.Format32bppPArgb);
-
-            using (var graphics = Graphics.FromImage(img))
-            {
-                graphics.CompositingQuality = CompositingQuality.HighQuality;
-                graphics.SmoothingMode = SmoothingMode.HighQuality;
-                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
-                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
-                graphics.CompositingMode = CompositingMode.SourceCopy;
-
-                for (var row = 0; row < rows; row++)
-                {
-                    for (var col = 0; col < cols; col++)
-                    {
-                        var x = col * (cellWidth / 2);
-                        var y = row * cellHeight;
-
-                        if (files.Count > index)
-                        {
-                            using (var fileStream = _fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
-                            {
-                                using (var memoryStream = new MemoryStream())
-                                {
-                                    await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
-                                    memoryStream.Position = 0;
-
-                                    using (var imgtemp = Image.FromStream(memoryStream, true, false))
-                                    {
-                                        graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
-                                    }
-                                }
-                            }
-                        }
-
-                        index++;
-                    }
-                }
-            }
-
-            return img;
-        }
-
-        private const int SquareImageSize = 800;
-        private const int ThumbImageWidth = 1600;
-        private const int ThumbImageHeight = 900;
-
-        private async Task<Image> GetSquareCollage(List<string> files)
-        {
-            if (files.Count < 4)
-            {
-                return await GetSingleImage(files).ConfigureAwait(false);
-            }
-
-            const int rows = 2;
-            const int cols = 2;
-
-            const int singleSize = SquareImageSize / 2;
-            var index = 0;
-
-            var img = new Bitmap(SquareImageSize, SquareImageSize, PixelFormat.Format32bppPArgb);
-
-            using (var graphics = Graphics.FromImage(img))
-            {
-                graphics.CompositingQuality = CompositingQuality.HighQuality;
-                graphics.SmoothingMode = SmoothingMode.HighQuality;
-                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
-                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
-                graphics.CompositingMode = CompositingMode.SourceCopy;
-
-                for (var row = 0; row < rows; row++)
-                {
-                    for (var col = 0; col < cols; col++)
-                    {
-                        var x = col * singleSize;
-                        var y = row * singleSize;
-
-                        using (var fileStream = _fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
-                        {
-                            using (var memoryStream = new MemoryStream())
-                            {
-                                await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
-                                memoryStream.Position = 0;
-
-                                using (var imgtemp = Image.FromStream(memoryStream, true, false))
-                                {
-                                    graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
-                                }
-                            }
-                        }
-
-                        index++;
-                    }
-                }
-            }
-
-            return img;
-        }
-
-        private Task<Image> GetSingleImage(List<string> files)
-        {
-            return GetImage(files[0]);
-        }
-
-        private async Task<Image> GetImage(string file)
-        {
-            using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true))
-            {
-                var memoryStream = new MemoryStream();
-
-                await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
-                memoryStream.Position = 0;
-
-                return Image.FromStream(memoryStream, true, false);
-            }
-        }
-
-        public string Name
+        public PhotoAlbumImageProvider(IFileSystem fileSystem, IProviderManager providerManager)
+            : base(fileSystem, providerManager)
         {
-            get { return "Dynamic Image Provider"; }
         }
 
-        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
+        protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
         {
-            var items = GetItemsWithImages(item);
-            var cacheKey = GetConfigurationCacheKey(items);
+            var photoAlbum = (PhotoAlbum)item;
+            var items = GetFinalItems(photoAlbum.RecursiveChildren.Where(i => i is Photo).ToList());
 
-            return HasChanged(item, ImageType.Primary, cacheKey) || HasChanged(item, ImageType.Thumb, cacheKey);
+            return Task.FromResult(items);
         }
     }
 }

+ 6 - 285
MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs

@@ -1,123 +1,26 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Server.Implementations.Photos;
 using MoreLinq;
-using System;
 using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
-using System.IO;
 using System.Linq;
-using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Server.Implementations.Playlists
 {
-    public class PlaylistImageProvider : ICustomMetadataProvider<Playlist>, IHasChangeMonitor
+    public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist>, ICustomMetadataProvider<Playlist>
     {
-        private readonly IFileSystem _fileSystem;
-        private readonly IProviderManager _provider;
-
-        public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager provider)
-        {
-            _fileSystem = fileSystem;
-            _provider = provider;
-        }
-
-        public async Task<ItemUpdateType> FetchAsync(Playlist item, MetadataRefreshOptions options, CancellationToken cancellationToken)
-        {
-            var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false);
-            var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false);
-
-            return primaryResult | thumbResult;
-        }
-
-        public async Task<ItemUpdateType> FetchAsync(PhotoAlbum item, MetadataRefreshOptions options, CancellationToken cancellationToken)
-        {
-            var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false);
-            var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false);
-
-            return primaryResult | thumbResult;
-        }
-
-        private Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
-        {
-            var items = GetItemsWithImages(item);
-            var cacheKey = GetConfigurationCacheKey(items);
-
-            if (!HasChanged(item, imageType, cacheKey))
-            {
-                return Task.FromResult(ItemUpdateType.None);
-            }
-
-            return FetchAsyncInternal(item, imageType, cacheKey, options, cancellationToken);
-        }
-
-        private async Task<ItemUpdateType> FetchAsyncInternal(IHasImages item, ImageType imageType, string cacheKey, MetadataRefreshOptions options, CancellationToken cancellationToken)
-        {
-            var img = await CreateImageAsync(item, imageType, 0).ConfigureAwait(false);
-
-            if (img == null)
-            {
-                return ItemUpdateType.None;
-            }
-
-            using (var ms = new MemoryStream())
-            {
-                img.Save(ms, ImageFormat.Png);
-
-                ms.Position = 0;
-
-                await _provider.SaveImage(item, ms, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false);
-            }
-
-            return ItemUpdateType.ImageUpdate;
-        }
-
-        private bool HasChanged(IHasImages item, ImageType type, string cacheKey)
-        {
-            var image = item.GetImageInfo(type, 0);
-
-            if (image != null)
-            {
-                if (!_fileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
-                {
-                    return false;
-                }
-
-                var currentPathCacheKey = (Path.GetFileNameWithoutExtension(image.Path) ?? string.Empty).Split('_').LastOrDefault();
-
-                if (string.Equals(cacheKey, currentPathCacheKey, StringComparison.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        private const string Version = "3";
-
-        public string GetConfigurationCacheKey(List<BaseItem> items)
+        public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager) : base(fileSystem, providerManager)
         {
-            return (Version + "_" + string.Join(",", items.Select(i => i.Id.ToString("N")).ToArray())).GetMD5().ToString("N");
         }
 
-        private List<BaseItem> GetItemsWithImages(IHasImages item)
+        protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
         {
-            var photoAlbum = item as PhotoAlbum;
-            if (photoAlbum != null)
-            {
-                return GetFinalItems(photoAlbum.RecursiveChildren.Where(i => i is Photo).ToList());
-            }
-
             var playlist = (Playlist)item;
 
             var items = playlist.GetManageableItems()
@@ -157,189 +60,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
                 .DistinctBy(i => i.Id)
                 .ToList();
 
-            return GetFinalItems(items);
-        }
-
-        private List<BaseItem> GetFinalItems(List<BaseItem> items)
-        {
-            // Rotate the images no more than once per day
-            var random = new Random(DateTime.Now.DayOfYear).Next();
-
-            return items
-                .OrderBy(i => random - items.IndexOf(i))
-                .Take(4)
-                .OrderBy(i => i.Name)
-                .ToList();
-        }
-
-        public async Task<Image> CreateImageAsync(IHasImages item, ImageType imageType, int imageIndex)
-        {
-            var items = GetItemsWithImages(item);
-
-            if (items.Count == 0)
-            {
-                return null;
-            }
-
-            return imageType == ImageType.Thumb ?
-                await GetThumbCollage(items).ConfigureAwait(false) :
-                await GetSquareCollage(items).ConfigureAwait(false);
-        }
-
-        private Task<Image> GetThumbCollage(List<BaseItem> items)
-        {
-            return GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList());
-        }
-
-        private Task<Image> GetSquareCollage(List<BaseItem> items)
-        {
-            return GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList());
-        }
-
-        private async Task<Image> GetThumbCollage(List<string> files)
-        {
-            if (files.Count < 3)
-            {
-                return await GetSingleImage(files).ConfigureAwait(false);
-            }
-
-            const int rows = 1;
-            const int cols = 3;
-
-            const int cellWidth = 2 * (ThumbImageWidth / 3);
-            const int cellHeight = ThumbImageHeight;
-            var index = 0;
-
-            var img = new Bitmap(ThumbImageWidth, ThumbImageHeight, PixelFormat.Format32bppPArgb);
-
-            using (var graphics = Graphics.FromImage(img))
-            {
-                graphics.CompositingQuality = CompositingQuality.HighQuality;
-                graphics.SmoothingMode = SmoothingMode.HighQuality;
-                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
-                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
-                graphics.CompositingMode = CompositingMode.SourceCopy;
-
-                for (var row = 0; row < rows; row++)
-                {
-                    for (var col = 0; col < cols; col++)
-                    {
-                        var x = col * (cellWidth / 2);
-                        var y = row * cellHeight;
-
-                        if (files.Count > index)
-                        {
-                            using (var fileStream = _fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
-                            {
-                                using (var memoryStream = new MemoryStream())
-                                {
-                                    await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
-                                    memoryStream.Position = 0;
-
-                                    using (var imgtemp = Image.FromStream(memoryStream, true, false))
-                                    {
-                                        graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
-                                    }
-                                }
-                            }
-                        }
-
-                        index++;
-                    }
-                }
-            }
-
-            return img;
-        }
-
-        private const int SquareImageSize = 800;
-        private const int ThumbImageWidth = 1600;
-        private const int ThumbImageHeight = 900;
-        
-        private async Task<Image> GetSquareCollage(List<string> files)
-        {
-            if (files.Count < 4)
-            {
-                return await GetSingleImage(files).ConfigureAwait(false);
-            }
-
-            const int rows = 2;
-            const int cols = 2;
-
-            const int singleSize = SquareImageSize / 2;
-            var index = 0;
-
-            var img = new Bitmap(SquareImageSize, SquareImageSize, PixelFormat.Format32bppPArgb);
-
-            using (var graphics = Graphics.FromImage(img))
-            {
-                graphics.CompositingQuality = CompositingQuality.HighQuality;
-                graphics.SmoothingMode = SmoothingMode.HighQuality;
-                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
-                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
-                graphics.CompositingMode = CompositingMode.SourceCopy;
-
-                for (var row = 0; row < rows; row++)
-                {
-                    for (var col = 0; col < cols; col++)
-                    {
-                        var x = col * singleSize;
-                        var y = row * singleSize;
-
-                        using (var fileStream = _fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
-                        {
-                            using (var memoryStream = new MemoryStream())
-                            {
-                                await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
-                                memoryStream.Position = 0;
-
-                                using (var imgtemp = Image.FromStream(memoryStream, true, false))
-                                {
-                                    graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
-                                }
-                            }
-                        }
-
-                        index++;
-                    }
-                }
-            }
-
-            return img;
-        }
-
-        private Task<Image> GetSingleImage(List<string> files)
-        {
-            return GetImage(files[0]);
-        }
-
-        private async Task<Image> GetImage(string file)
-        {
-            using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true))
-            {
-                var memoryStream = new MemoryStream();
-
-                await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
-                memoryStream.Position = 0;
-
-                return Image.FromStream(memoryStream, true, false);
-            }
-        }
-
-        public string Name
-        {
-            get { return "Dynamic Image Provider"; }
-        }
-
-        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
-        {
-            var items = GetItemsWithImages(item);
-            var cacheKey = GetConfigurationCacheKey(items);
-
-            return HasChanged(item, ImageType.Primary, cacheKey) || HasChanged(item, ImageType.Thumb, cacheKey);
+            return Task.FromResult(GetFinalItems(items));
         }
     }
 }

+ 7 - 0
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -189,4 +189,11 @@
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
   </ItemGroup>
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
 </Project>

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

@@ -266,4 +266,11 @@ del "$(SolutionDir)..\Deploy\MBServer.zip"
     </GetAssemblyIdentity>
     <Exec Command="copy &quot;$(SolutionDir)..\Deploy\MBServer.zip&quot;  &quot;$(SolutionDir)..\Deploy\MBServer_%(CurrentAssembly.Version).zip&quot; /y" Condition="'$(ConfigurationName)' == 'Release'" />
   </Target>
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
 </Project>

+ 2 - 0
MediaBrowser.WebDashboard/Api/PackageCreator.cs

@@ -458,7 +458,9 @@ namespace MediaBrowser.WebDashboard.Api
                                 "tvstudios.js",
                                 "tvupcoming.js",
                                 "useredit.js",
+                                "usernew.js",
                                 "myprofile.js",
+                                "userpassword.js",
                                 "userprofilespage.js",
                                 "userparentalcontrol.js",
                                 "userlibraryaccess.js",

+ 19 - 0
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -89,6 +89,12 @@
     <Content Include="dashboard-ui\scripts\selectserver.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\usernew.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="dashboard-ui\scripts\userpassword.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\selectserver.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -1592,9 +1598,15 @@
     <Content Include="dashboard-ui\userlibraryaccess.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\usernew.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\userparentalcontrol.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\userpassword.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\wizardservice.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -2285,6 +2297,13 @@
     <PostBuildEvent>
     </PostBuildEvent>
   </PropertyGroup>
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">