Переглянути джерело

fixes #945 - Add genre views to dlna

Luke Pulverenti 10 роки тому
батько
коміт
1fea9ad926
41 змінених файлів з 554 додано та 172 видалено
  1. 1 1
      MediaBrowser.Api/ConfigurationService.cs
  2. 1 3
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  3. 5 2
      MediaBrowser.Api/Playback/StreamState.cs
  4. 1 1
      MediaBrowser.Controller/Entities/UserView.cs
  5. 176 64
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  6. 3 0
      MediaBrowser.Controller/Library/IUserViewManager.cs
  7. 10 1
      MediaBrowser.LocalMetadata/BaseXmlProvider.cs
  8. 1 1
      MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
  9. 1 1
      MediaBrowser.LocalMetadata/Savers/ChannelXmlSaver.cs
  10. 1 1
      MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs
  11. 1 1
      MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs
  12. 1 1
      MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs
  13. 1 1
      MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs
  14. 1 1
      MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs
  15. 1 1
      MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs
  16. 1 1
      MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
  17. 1 1
      MediaBrowser.LocalMetadata/Savers/SeasonXmlSaver.cs
  18. 1 1
      MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs
  19. 10 0
      MediaBrowser.Model/ApiClient/IApiClient.cs
  20. 8 0
      MediaBrowser.Model/ApiClient/IConnectionManager.cs
  21. 4 0
      MediaBrowser.Model/Entities/CollectionType.cs
  22. 6 1
      MediaBrowser.Server.Implementations/Library/UserViewManager.cs
  23. 1 1
      MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
  24. 5 1
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  25. 42 1
      MediaBrowser.Server.Implementations/Music/MusicDynamicImageProvider.cs
  26. 3 0
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  27. 2 0
      MediaBrowser.Server.Mono/mediabrowser.sh
  28. 5 78
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  29. 5 0
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  30. 8 0
      MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs
  31. 36 0
      MediaBrowser.Server.Startup.Common/Migrations/MigrateUserFolders.cs
  32. 55 0
      MediaBrowser.Server.Startup.Common/Migrations/PlaylistImages.cs
  33. 56 0
      MediaBrowser.Server.Startup.Common/Migrations/RenameXbmcOptions.cs
  34. 56 0
      MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs
  35. 29 0
      MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
  36. 8 0
      MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
  37. 1 1
      MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
  38. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  39. 1 1
      Nuget/MediaBrowser.Common.nuspec
  40. 1 1
      Nuget/MediaBrowser.Model.Signed.nuspec
  41. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 1 - 1
MediaBrowser.Api/ConfigurationService.cs

@@ -124,7 +124,7 @@ namespace MediaBrowser.Api
 
         public void Post(AutoSetMetadataOptions request)
         {
-            _configurationManager.DisableMetadataService("Media Browser Xml");
+            _configurationManager.DisableMetadataService("Media Browser Legacy Xml");
             _configurationManager.SaveConfiguration();
         }
 

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

@@ -995,7 +995,7 @@ namespace MediaBrowser.Api.Playback
 
                 if (state.ReadInputAtNativeFramerate)
                 {
-                    await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
+                    await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
                 }
             }
 
@@ -1619,8 +1619,6 @@ namespace MediaBrowser.Api.Playback
 
             AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
 
-            state.SegmentLength = 6;
-
             var container = Path.GetExtension(state.RequestedUrl);
 
             if (string.IsNullOrEmpty(container))

+ 5 - 2
MediaBrowser.Api/Playback/StreamState.cs

@@ -63,11 +63,14 @@ namespace MediaBrowser.Api.Playback
 
         public string LiveTvStreamId { get; set; }
 
-        public int SegmentLength = 10;
+        public int SegmentLength = 6;
 
         public int HlsListSize
         {
-            get { return ReadInputAtNativeFramerate ? 100 : 1440; }
+            get
+            {
+                return ReadInputAtNativeFramerate ? 1000 : 0;
+            }
         }
 
         public long? RunTimeTicks;

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

@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Entities
             }
 
             return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager)
-                .GetUserItems(parent, ViewType, query);
+                .GetUserItems(parent, this, ViewType, query);
         }
 
         public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)

+ 176 - 64
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities
             _collectionManager = collectionManager;
         }
 
-        public async Task<QueryResult<BaseItem>> GetUserItems(Folder parent, string viewType, InternalItemsQuery query)
+        public async Task<QueryResult<BaseItem>> GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query)
         {
             var user = query.User;
 
@@ -107,110 +107,125 @@ namespace MediaBrowser.Controller.Entities
                     {
                         var result = await GetLiveTvFolders(user).ConfigureAwait(false);
 
-                        return GetResult(result, parent, query);
+                        return GetResult(result, queryParent, query);
                     }
 
                 case CollectionType.Folders:
-                    return GetResult(user.RootFolder.GetChildren(user, true), parent, query);
+                    return GetResult(user.RootFolder.GetChildren(user, true), queryParent, query);
 
                 case CollectionType.Games:
-                    return await GetGameView(user, parent, query).ConfigureAwait(false);
+                    return await GetGameView(user, queryParent, query).ConfigureAwait(false);
 
                 case CollectionType.BoxSets:
-                    return GetResult(GetMediaFolders(user).SelectMany(i => i.GetRecursiveChildren(user)).OfType<BoxSet>(), parent, query);
+                    return GetResult(GetMediaFolders(user).SelectMany(i => i.GetRecursiveChildren(user)).OfType<BoxSet>(), queryParent, query);
 
                 case CollectionType.TvShows:
-                    return await GetTvView(parent, user, query).ConfigureAwait(false);
+                    return await GetTvView(queryParent, user, query).ConfigureAwait(false);
 
                 case CollectionType.Music:
-                    return await GetMusicFolders(parent, user, query).ConfigureAwait(false);
+                    return await GetMusicFolders(queryParent, user, query).ConfigureAwait(false);
 
                 case CollectionType.Movies:
-                    return await GetMovieFolders(parent, user, query).ConfigureAwait(false);
+                    return await GetMovieFolders(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.MusicGenres:
+                    return await GetMusicGenres(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.MusicGenre:
+                    return await GetMusicGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
 
                 case SpecialFolder.GameGenres:
-                    return GetGameGenres(parent, user, query);
+                    return await GetGameGenres(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.GameGenre:
+                    return await GetGameGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
 
                 case SpecialFolder.GameSystems:
-                    return GetGameSystems(parent, user, query);
+                    return GetGameSystems(queryParent, user, query);
 
                 case SpecialFolder.LatestGames:
-                    return GetLatestGames(parent, user, query);
+                    return GetLatestGames(queryParent, user, query);
 
                 case SpecialFolder.RecentlyPlayedGames:
-                    return GetRecentlyPlayedGames(parent, user, query);
+                    return GetRecentlyPlayedGames(queryParent, user, query);
 
                 case SpecialFolder.GameFavorites:
-                    return GetFavoriteGames(parent, user, query);
+                    return GetFavoriteGames(queryParent, user, query);
 
                 case SpecialFolder.TvShowSeries:
-                    return GetTvSeries(parent, user, query);
+                    return GetTvSeries(queryParent, user, query);
 
                 case SpecialFolder.TvGenres:
-                    return GetTvGenres(parent, user, query);
+                    return await GetTvGenres(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.TvGenre:
+                    return await GetTvGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
 
                 case SpecialFolder.TvResume:
-                    return GetTvResume(parent, user, query);
+                    return GetTvResume(queryParent, user, query);
 
                 case SpecialFolder.TvNextUp:
-                    return GetTvNextUp(parent, query);
+                    return GetTvNextUp(queryParent, query);
 
                 case SpecialFolder.TvLatest:
-                    return GetTvLatest(parent, user, query);
+                    return GetTvLatest(queryParent, user, query);
 
                 case SpecialFolder.MovieFavorites:
-                    return GetFavoriteMovies(parent, user, query);
+                    return GetFavoriteMovies(queryParent, user, query);
 
                 case SpecialFolder.MovieLatest:
-                    return GetMovieLatest(parent, user, query);
+                    return GetMovieLatest(queryParent, user, query);
 
                 case SpecialFolder.MovieGenres:
-                    return GetMovieGenres(parent, user, query);
+                    return await GetMovieGenres(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.MovieGenre:
+                    return await GetMovieGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
 
                 case SpecialFolder.MovieResume:
-                    return GetMovieResume(parent, user, query);
+                    return GetMovieResume(queryParent, user, query);
 
                 case SpecialFolder.MovieMovies:
-                    return GetMovieMovies(parent, user, query);
+                    return GetMovieMovies(queryParent, user, query);
 
                 case SpecialFolder.MovieCollections:
-                    return GetMovieCollections(parent, user, query);
+                    return GetMovieCollections(queryParent, user, query);
 
                 case SpecialFolder.MusicLatest:
-                    return GetMusicLatest(parent, user, query);
+                    return GetMusicLatest(queryParent, user, query);
 
                 case SpecialFolder.MusicAlbums:
-                    return GetMusicAlbums(parent, user, query);
+                    return GetMusicAlbums(queryParent, user, query);
 
                 case SpecialFolder.MusicAlbumArtists:
-                    return GetMusicAlbumArtists(parent, user, query);
+                    return GetMusicAlbumArtists(queryParent, user, query);
 
                 case SpecialFolder.MusicArtists:
-                    return GetMusicArtists(parent, user, query);
+                    return GetMusicArtists(queryParent, user, query);
 
                 case SpecialFolder.MusicSongs:
-                    return GetMusicSongs(parent, user, query);
+                    return GetMusicSongs(queryParent, user, query);
 
                 case SpecialFolder.TvFavoriteEpisodes:
-                    return GetFavoriteEpisodes(parent, user, query);
+                    return GetFavoriteEpisodes(queryParent, user, query);
 
                 case SpecialFolder.TvFavoriteSeries:
-                    return GetFavoriteSeries(parent, user, query);
+                    return GetFavoriteSeries(queryParent, user, query);
 
                 case SpecialFolder.MusicFavorites:
-                    return await GetMusicFavorites(parent, user, query).ConfigureAwait(false);
+                    return await GetMusicFavorites(queryParent, user, query).ConfigureAwait(false);
 
                 case SpecialFolder.MusicFavoriteAlbums:
-                    return GetFavoriteAlbums(parent, user, query);
+                    return GetFavoriteAlbums(queryParent, user, query);
 
                 case SpecialFolder.MusicFavoriteArtists:
-                    return GetFavoriteArtists(parent, user, query);
+                    return GetFavoriteArtists(queryParent, user, query);
 
                 case SpecialFolder.MusicFavoriteSongs:
-                    return GetFavoriteSongs(parent, user, query);
+                    return GetFavoriteSongs(queryParent, user, query);
 
                 default:
-                    return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), parent, query);
+                    return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), queryParent, query);
             }
         }
 
@@ -231,9 +246,9 @@ namespace MediaBrowser.Controller.Entities
             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.MusicArtists, user, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicSongs, user, "4", 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);
@@ -250,6 +265,59 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(list, parent, query);
         }
 
+        private async Task<QueryResult<BaseItem>> GetMusicGenres(Folder parent, User user, InternalItemsQuery query)
+        {
+            var tasks = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
+                .Where(i => !i.IsFolder)
+                .SelectMany(i => i.Genres)
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .Select(i =>
+                {
+                    try
+                    {
+                        return _libraryManager.GetMusicGenre(i);
+                    }
+                    catch
+                    {
+                        // Full exception logged at lower levels
+                        _logger.Error("Error getting genre");
+                        return null;
+                    }
+
+                })
+                .Where(i => i != null)
+                .Select(i => GetUserView(i.Name, SpecialFolder.MusicGenre, user, i.SortName, parent));
+
+            var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
+
+            return GetResult(genres, parent, query);
+        }
+
+        private async Task<QueryResult<BaseItem>> GetMusicGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
+        {
+            var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
+                .Where(i => !i.IsFolder)
+                .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase))
+                .OfType<IHasAlbumArtist>()
+                .SelectMany(i => i.AlbumArtists)
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .Select(i =>
+                {
+                    try
+                    {
+                        return _libraryManager.GetArtist(i);
+                    }
+                    catch
+                    {
+                        // Already logged at lower levels
+                        return null;
+                    }
+                })
+                .Where(i => i != null);
+
+            return GetResult(items, queryParent, query);
+        }
+
         private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
         {
             var artists = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
@@ -354,7 +422,7 @@ namespace MediaBrowser.Controller.Entities
             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));
+            list.Add(await GetUserView(SpecialFolder.MovieGenres, user, "5", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -421,9 +489,9 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), parent, GetSpecialItemsLimit(), query);
         }
 
-        private QueryResult<BaseItem> GetMovieGenres(Folder parent, User user, InternalItemsQuery query)
+        private async Task<QueryResult<BaseItem>> GetMovieGenres(Folder parent, User user, InternalItemsQuery query)
         {
-            var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty })
+            var tasks = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty })
                 .Where(i => i is Movie)
                 .SelectMany(i => i.Genres)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
@@ -441,11 +509,23 @@ namespace MediaBrowser.Controller.Entities
                     }
 
                 })
-                .Where(i => i != null);
+                .Where(i => i != null)
+                .Select(i => GetUserView(i.Name, SpecialFolder.MovieGenre, user, i.SortName, parent));
+
+            var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
 
             return GetResult(genres, parent, query);
         }
 
+        private async Task<QueryResult<BaseItem>> GetMovieGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
+        {
+            var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty })
+                .Where(i => i is Movie)
+                .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase));
+
+            return GetResult(items, queryParent, query);
+        }
+
         private async Task<QueryResult<BaseItem>> GetTvView(Folder parent, User user, InternalItemsQuery query)
         {
             if (query.Recursive)
@@ -461,7 +541,7 @@ namespace MediaBrowser.Controller.Entities
             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));
+            list.Add(await GetUserView(SpecialFolder.TvGenres, user, "6", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -479,7 +559,7 @@ namespace MediaBrowser.Controller.Entities
             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));
+            list.Add(await GetUserView(SpecialFolder.GameGenres, user, "4", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -545,9 +625,9 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Series>(), parent, query);
         }
 
-        private QueryResult<BaseItem> GetTvGenres(Folder parent, User user, InternalItemsQuery query)
+        private async Task<QueryResult<BaseItem>> GetTvGenres(Folder parent, User user, InternalItemsQuery query)
         {
-            var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty })
+            var tasks = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty })
                 .OfType<Series>()
                 .SelectMany(i => i.Genres)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
@@ -565,19 +645,40 @@ namespace MediaBrowser.Controller.Entities
                     }
 
                 })
-                .Where(i => i != null);
+                .Where(i => i != null)
+                .Select(i => GetUserView(i.Name, SpecialFolder.TvGenre, user, i.SortName, parent));
+
+            var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
 
             return GetResult(genres, parent, query);
         }
 
+        private async Task<QueryResult<BaseItem>> GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
+        {
+            var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.TvShows, string.Empty })
+                .Where(i => i is Series)
+                .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase));
+
+            return GetResult(items, queryParent, query);
+        }
+
         private QueryResult<BaseItem> GetGameSystems(Folder parent, User user, InternalItemsQuery query)
         {
             return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<GameSystem>(), parent, query);
         }
 
-        private QueryResult<BaseItem> GetGameGenres(Folder parent, User user, InternalItemsQuery query)
+        private async Task<QueryResult<BaseItem>> GetGameGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
+        {
+            var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Games })
+                .OfType<Game>()
+                .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase));
+
+            return GetResult(items, queryParent, query);
+        }
+
+        private async Task<QueryResult<BaseItem>> GetGameGenres(Folder parent, User user, InternalItemsQuery query)
         {
-            var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.Games })
+            var tasks = GetRecursiveChildren(parent, user, new[] { CollectionType.Games })
                 .OfType<Game>()
                 .SelectMany(i => i.Genres)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
@@ -595,7 +696,10 @@ namespace MediaBrowser.Controller.Entities
                     }
 
                 })
-                .Where(i => i != null);
+                .Where(i => i != null)
+                .Select(i => GetUserView(i.Name, SpecialFolder.GameGenre, user, i.SortName, parent));
+
+            var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
 
             return GetResult(genres, parent, query);
         }
@@ -611,24 +715,24 @@ namespace MediaBrowser.Controller.Entities
         }
 
         private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items,
-            BaseItem parentItem,
+            BaseItem queryParent,
             InternalItemsQuery query)
             where T : BaseItem
         {
-            return GetResult(items, parentItem, null, query);
+            return GetResult(items, queryParent, null, query);
         }
 
         private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items,
-            BaseItem parentItem,
+            BaseItem queryParent,
             int? totalRecordLimit,
             InternalItemsQuery query)
             where T : BaseItem
         {
-            return SortAndFilter(items, parentItem, totalRecordLimit, query, _libraryManager, _userDataManager);
+            return SortAndFilter(items, queryParent, totalRecordLimit, query, _libraryManager, _userDataManager);
         }
 
         public static QueryResult<BaseItem> SortAndFilter(IEnumerable<BaseItem> items,
-            BaseItem parentItem,
+            BaseItem queryParent,
             int? totalRecordLimit,
             InternalItemsQuery query,
             ILibraryManager libraryManager,
@@ -643,7 +747,7 @@ namespace MediaBrowser.Controller.Entities
                 query.IsVirtualUnaired,
                 query.IsUnaired);
 
-            items = CollapseBoxSetItemsIfNeeded(items, query, parentItem, user);
+            items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user);
 
             // This must be the last filter
             if (!string.IsNullOrEmpty(query.AdjacentTo))
@@ -656,10 +760,10 @@ namespace MediaBrowser.Controller.Entities
 
         public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items,
             InternalItemsQuery query,
-            BaseItem parentItem,
+            BaseItem queryParent,
             User user)
         {
-            if (CollapseBoxSetItems(query, parentItem, user))
+            if (CollapseBoxSetItems(query, queryParent, user))
             {
                 items = BaseItem.CollectionManager.CollapseItemsWithinBoxSets(items, user);
             }
@@ -691,11 +795,11 @@ namespace MediaBrowser.Controller.Entities
         }
 
         private static bool CollapseBoxSetItems(InternalItemsQuery query,
-            BaseItem parentItem,
+            BaseItem queryParent,
             User user)
         {
             // Could end up stuck in a loop like this
-            if (parentItem is BoxSet)
+            if (queryParent is BoxSet)
             {
                 return false;
             }
@@ -1488,7 +1592,7 @@ namespace MediaBrowser.Controller.Entities
                 });
         }
 
-        private IEnumerable<Folder> GetMediaFolders(Folder parent, User user, string[] viewTypes)
+        private IEnumerable<Folder> GetMediaFolders(Folder parent, User user, IEnumerable<string> viewTypes)
         {
             if (parent == null || parent is UserView)
             {
@@ -1498,7 +1602,7 @@ namespace MediaBrowser.Controller.Entities
             return new[] { parent };
         }
 
-        private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, string[] viewTypes)
+        private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, IEnumerable<string> viewTypes)
         {
             if (parent == null || parent is UserView)
             {
@@ -1521,7 +1625,15 @@ namespace MediaBrowser.Controller.Entities
             return list;
         }
 
-        private async Task<UserView> GetUserView(string type, User user, string sortName, Folder parent)
+        private async Task<UserView> GetUserView(string name, string type, User user, string sortName, BaseItem parent)
+        {
+            var view = await _userViewManager.GetUserView(name, parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
+                        .ConfigureAwait(false);
+
+            return view;
+        }
+
+        private async Task<UserView> GetUserView(string type, User user, string sortName, BaseItem parent)
         {
             var view = await _userViewManager.GetUserView(parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
                         .ConfigureAwait(false);

+ 3 - 0
MediaBrowser.Controller/Library/IUserViewManager.cs

@@ -10,6 +10,9 @@ namespace MediaBrowser.Controller.Library
     {
         Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken);
 
+        Task<UserView> GetUserView(string name, string parentId, string type, User user, string sortName,
+            CancellationToken cancellationToken);
+
         Task<UserView> GetUserView(string type, string sortName, CancellationToken cancellationToken);
 
         Task<UserView> GetUserView(string category, string type, User user, string sortName, CancellationToken cancellationToken);

+ 10 - 1
MediaBrowser.LocalMetadata/BaseXmlProvider.cs

@@ -78,13 +78,22 @@ namespace MediaBrowser.LocalMetadata
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
+
     }
 
     static class XmlProviderUtils
     {
+        public static string Name
+        {
+            get
+            {
+                return "Media Browser Legacy Xml";
+            }
+        }
+        
         internal static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4);
     }
 }

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/ChannelXmlSaver.cs

@@ -41,7 +41,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs

@@ -45,7 +45,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs

@@ -18,7 +18,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs

@@ -30,7 +30,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs

@@ -18,7 +18,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs

@@ -16,7 +16,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/SeasonXmlSaver.cs

@@ -17,7 +17,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs

@@ -25,7 +25,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         {
             get
             {
-                return "Media Browser Xml";
+                return XmlProviderUtils.Name;
             }
         }
 

+ 10 - 0
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -121,6 +121,16 @@ namespace MediaBrowser.Model.ApiClient
         /// <returns>Task{SearchHintResult}.</returns>
         Task<SearchHintResult> GetSearchHintsAsync(SearchQuery query);
 
+        /// <summary>
+        /// Gets the filters.
+        /// </summary>
+        /// <param name="userId">The user identifier.</param>
+        /// <param name="parentId">The parent identifier.</param>
+        /// <param name="mediaTypes">The media types.</param>
+        /// <param name="itemTypes">The item types.</param>
+        /// <returns>Task&lt;QueryFilters&gt;.</returns>
+        Task<QueryFilters> GetFilters(string userId, string parentId, string[] mediaTypes, string[] itemTypes);
+
         /// <summary>
         /// Gets the theme videos async.
         /// </summary>

+ 8 - 0
MediaBrowser.Model/ApiClient/IConnectionManager.cs

@@ -60,6 +60,14 @@ namespace MediaBrowser.Model.ApiClient
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;ConnectionResult&gt;.</returns>
         Task<ConnectionResult> Connect(CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Connects the specified API client.
+        /// </summary>
+        /// <param name="apiClient">The API client.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;ConnectionResult&gt;.</returns>
+        Task<ConnectionResult> Connect(IApiClient apiClient, CancellationToken cancellationToken);
         
         /// <summary>
         /// Connects the specified server.

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

@@ -35,6 +35,7 @@
 
         public const string TvShowSeries = "TvShowSeries";
         public const string TvGenres = "TvGenres";
+        public const string TvGenre = "TvGenre";
         public const string TvLatest = "TvLatest";
         public const string TvNextUp = "TvNextUp";
         public const string TvResume = "TvResume";
@@ -47,17 +48,20 @@
         public const string MovieCollections = "MovieCollections";
         public const string MovieFavorites = "MovieFavorites";
         public const string MovieGenres = "MovieGenres";
+        public const string MovieGenre = "MovieGenre";
 
         public const string LatestGames = "LatestGames";
         public const string RecentlyPlayedGames = "RecentlyPlayedGames";
         public const string GameSystems = "GameSystems";
         public const string GameGenres = "GameGenres";
         public const string GameFavorites = "GameFavorites";
+        public const string GameGenre = "GameGenre";
 
         public const string MusicArtists = "MusicArtists";
         public const string MusicAlbumArtists = "MusicAlbumArtists";
         public const string MusicAlbums = "MusicAlbums";
         public const string MusicGenres = "MusicGenres";
+        public const string MusicGenre = "MusicGenre";
         public const string MusicLatest = "MusicLatest";
         public const string MusicSongs = "MusicSongs";
         public const string MusicFavorites = "MusicFavorites";

+ 6 - 1
MediaBrowser.Server.Implementations/Library/UserViewManager.cs

@@ -148,11 +148,16 @@ namespace MediaBrowser.Server.Implementations.Library
                 .ThenBy(i => i.SortName);
         }
 
+        public Task<UserView> GetUserView(string name, string parentId, string type, User user, string sortName, CancellationToken cancellationToken)
+        {
+            return _libraryManager.GetSpecialFolder(user, name, parentId, type, sortName, cancellationToken);
+        }
+
         public Task<UserView> GetUserView(string parentId, string type, User user, string sortName, CancellationToken cancellationToken)
         {
             var name = _localizationManager.GetLocalizedString("ViewType" + type);
 
-            return _libraryManager.GetSpecialFolder(user, name, parentId, type, sortName, cancellationToken);
+            return GetUserView(name, parentId, type, user, sortName, cancellationToken);
         }
 
         public Task<UserView> GetUserView(string type, string sortName, CancellationToken cancellationToken)

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json

@@ -288,7 +288,7 @@
     "LabelPremiereProgram": "PREMIERE",
     "LabelHDProgram": "HD",
     "HeaderChangeFolderType": "Change Folder Type",
-    "HeaderChangeFolderTypeHelp": "To change the folder type, please remove and rebuild the collection with the new type.",
+    "HeaderChangeFolderTypeHelp": "To change the type, please remove and rebuild the folder with the new type.",
     "HeaderAlert": "Alert",
     "MessagePleaseRestart": "Please restart to finish updating.",
     "ButtonRestart": "Restart",

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

@@ -145,9 +145,11 @@
     "OptionBudget": "Budget",
     "OptionRevenue": "Revenue",
     "OptionPoster": "Poster",
+    "OptionPosterCard":  "Poster card",
     "OptionBackdrop": "Backdrop",
     "OptionTimeline": "Timeline",
     "OptionThumb": "Thumb",
+    "OptionThumbCard": "Thumb card",
     "OptionBanner": "Banner",
     "OptionCriticRating": "Critic Rating",
     "OptionVideoBitrate": "Video Bitrate",
@@ -455,7 +457,7 @@
     "LinkApiDocumentation": "Api Documentation",
     "LabelFriendlyServerName": "Friendly server name:",
     "LabelFriendlyServerNameHelp": "This name will be used to identify this server. If left blank, the computer name will be used.",
-    "LabelPreferredDisplayLanguage": "Preferred display language",
+    "LabelPreferredDisplayLanguage": "Preferred display language:",
     "LabelPreferredDisplayLanguageHelp": "Translating Media Browser is an ongoing project and is not yet complete.",
     "LabelReadHowYouCanContribute": "Read about how you can contribute.",
     "HeaderNewCollection": "New Collection",
@@ -847,6 +849,8 @@
     "ViewTypeTvShows": "TV",
     "ViewTypeGames": "Games",
     "ViewTypeMusic": "Music",
+    "ViewTypeMusicGenres": "Genres",
+    "ViewTypeMusicArtists": "Artists",
     "ViewTypeBoxSets": "Collections",
     "ViewTypeChannels": "Channels",
     "ViewTypeLiveTV": "Live TV",

+ 42 - 1
MediaBrowser.Server.Implementations/Music/MusicDynamicImageProvider.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
@@ -8,6 +9,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
+using MoreLinq;
 
 namespace MediaBrowser.Server.Implementations.Music
 {
@@ -36,7 +38,42 @@ namespace MediaBrowser.Server.Implementations.Music
 
             }).ConfigureAwait(false);
 
-            return GetFinalItems(result.Items.Where(i => i.HasImage(ImageType.Primary)).ToList());
+            var items = result.Items.Select(i =>
+            {
+                var episode = i as Episode;
+                if (episode != null)
+                {
+                    var series = episode.Series;
+                    if (series != null)
+                    {
+                        return series;
+                    }
+                    var episodeSeason = episode.Season;
+                    if (episodeSeason != null)
+                    {
+                        return episodeSeason;
+                    }
+
+                    return episode;
+                }
+
+                var season = i as Season;
+                if (season != null)
+                {
+                    var series = season.Series;
+                    if (series != null)
+                    {
+                        return series;
+                    }
+
+                    return season;
+                }
+
+                return i;
+
+            }).DistinctBy(i => i.Id);
+
+            return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary)).ToList());
         }
 
         protected override bool Supports(IHasImages item)
@@ -50,6 +87,7 @@ namespace MediaBrowser.Server.Implementations.Music
                     SpecialFolder.TvFavoriteEpisodes,
                     SpecialFolder.TvFavoriteSeries,
                     SpecialFolder.TvGenres,
+                    SpecialFolder.TvGenre,
                     SpecialFolder.TvLatest,
                     SpecialFolder.TvNextUp,
                     SpecialFolder.TvResume,
@@ -58,12 +96,14 @@ namespace MediaBrowser.Server.Implementations.Music
                     SpecialFolder.MovieCollections,
                     SpecialFolder.MovieFavorites,
                     SpecialFolder.MovieGenres,
+                    SpecialFolder.MovieGenre,
                     SpecialFolder.MovieLatest,
                     SpecialFolder.MovieMovies,
                     SpecialFolder.MovieResume,
 
                     SpecialFolder.GameFavorites,
                     SpecialFolder.GameGenres,
+                    SpecialFolder.GameGenre,
                     SpecialFolder.GameSystems,
                     SpecialFolder.LatestGames,
                     SpecialFolder.RecentlyPlayedGames,
@@ -72,6 +112,7 @@ namespace MediaBrowser.Server.Implementations.Music
                     SpecialFolder.MusicAlbumArtists,
                     SpecialFolder.MusicAlbums,
                     SpecialFolder.MusicGenres,
+                    SpecialFolder.MusicGenre,
                     SpecialFolder.MusicLatest,
                     SpecialFolder.MusicSongs,
                     SpecialFolder.MusicFavorites,

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

@@ -173,6 +173,9 @@
     <None Include="MediaBrowser.MediaInfo.dll.config">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
+    <None Include="mediabrowser.sh">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
     <None Include="packages.config" />
     <None Include="System.Data.SQLite.dll.config">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

+ 2 - 0
MediaBrowser.Server.Mono/mediabrowser.sh

@@ -0,0 +1,2 @@
+#!/bin/sh
+mono MediaBrowser.Server.Mono.exe

+ 5 - 78
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -82,6 +82,7 @@ using MediaBrowser.Server.Implementations.Sync;
 using MediaBrowser.Server.Implementations.Themes;
 using MediaBrowser.Server.Implementations.TV;
 using MediaBrowser.Server.Startup.Common.FFMpeg;
+using MediaBrowser.Server.Startup.Common.Migrations;
 using MediaBrowser.WebDashboard.Api;
 using MediaBrowser.XbmcMetadata.Providers;
 using System;
@@ -322,84 +323,10 @@ namespace MediaBrowser.Server.Startup.Common
 
         private void PerformVersionMigration()
         {
-            DeleteDeprecatedModules();
-
-            if (!ServerConfigurationManager.Configuration.PlaylistImagesDeleted)
-            {
-                DeletePlaylistImages();
-                ServerConfigurationManager.Configuration.PlaylistImagesDeleted = true;
-                ServerConfigurationManager.SaveConfiguration();
-            }
-        }
-
-        private void DeletePlaylistImages()
-        {
-            try
-            {
-                var path = Path.Combine(ApplicationPaths.DataPath, "playlists");
-
-                var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories)
-                    .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i) ?? string.Empty))
-                    .ToList();
-
-                foreach (var file in files)
-                {
-                    try
-                    {
-                        File.Delete(file);
-                    }
-                    catch (IOException)
-                    {
-
-                    }
-                }
-            }
-            catch (IOException)
-            {
-                
-            }
-        }
-
-        private void DeleteDeprecatedModules()
-        {
-            try
-            {
-                MigrateUserFolders();
-            }
-            catch (IOException)
-            {
-            }
-
-            try
-            {
-                File.Delete(Path.Combine(ApplicationPaths.PluginsPath, "MBPhoto.dll"));
-            }
-            catch (IOException)
-            {
-                // Not there, no big deal
-            }
-
-            try
-            {
-                File.Delete(Path.Combine(ApplicationPaths.PluginsPath, "MediaBrowser.Plugins.XbmcMetadata.dll"));
-            }
-            catch (IOException)
-            {
-                // Not there, no big deal
-            }
-        }
-
-        private void MigrateUserFolders()
-        {
-            var rootPath = ApplicationPaths.RootFolderPath;
-
-            var folders = new DirectoryInfo(rootPath).EnumerateDirectories("*", SearchOption.TopDirectoryOnly).Where(i => !string.Equals(i.Name, "default", StringComparison.OrdinalIgnoreCase))
-                .ToList();
-
-            foreach (var folder in folders)
-            {
-                Directory.Delete(folder.FullName, true);
-            }
+            new MigrateUserFolders(ApplicationPaths).Run();
+            new PlaylistImages(ServerConfigurationManager).Run();
+            new RenameXbmcOptions(ServerConfigurationManager).Run();
+            new RenameXmlOptions(ServerConfigurationManager).Run();
         }
 
         /// <summary>

+ 5 - 0
MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj

@@ -60,6 +60,11 @@
     <Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
     <Compile Include="FFMpeg\FFMpegInfo.cs" />
     <Compile Include="INativeApp.cs" />
+    <Compile Include="Migrations\IVersionMigration.cs" />
+    <Compile Include="Migrations\MigrateUserFolders.cs" />
+    <Compile Include="Migrations\PlaylistImages.cs" />
+    <Compile Include="Migrations\RenameXbmcOptions.cs" />
+    <Compile Include="Migrations\RenameXmlOptions.cs" />
     <Compile Include="NativeEnvironment.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="StartupOptions.cs" />

+ 8 - 0
MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs

@@ -0,0 +1,8 @@
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+    public interface IVersionMigration
+    {
+        void Run();
+    }
+}

+ 36 - 0
MediaBrowser.Server.Startup.Common/Migrations/MigrateUserFolders.cs

@@ -0,0 +1,36 @@
+using MediaBrowser.Controller;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+    public class MigrateUserFolders : IVersionMigration
+    {
+        private readonly IServerApplicationPaths _appPaths;
+
+        public MigrateUserFolders(IServerApplicationPaths appPaths)
+        {
+            _appPaths = appPaths;
+        }
+
+        public void Run()
+        {
+            try
+            {
+                var rootPath = _appPaths.RootFolderPath;
+
+                var folders = new DirectoryInfo(rootPath).EnumerateDirectories("*", SearchOption.TopDirectoryOnly).Where(i => !string.Equals(i.Name, "default", StringComparison.OrdinalIgnoreCase))
+                    .ToList();
+
+                foreach (var folder in folders)
+                {
+                    Directory.Delete(folder.FullName, true);
+                }
+            }
+            catch (IOException)
+            {
+            }
+        }
+    }
+}

+ 55 - 0
MediaBrowser.Server.Startup.Common/Migrations/PlaylistImages.cs

@@ -0,0 +1,55 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+    public class PlaylistImages : IVersionMigration
+    {
+        private readonly IServerConfigurationManager _config;
+
+        public PlaylistImages(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
+        public void Run()
+        {
+            if (!_config.Configuration.PlaylistImagesDeleted)
+            {
+                DeletePlaylistImages();
+                _config.Configuration.PlaylistImagesDeleted = true;
+                _config.SaveConfiguration();
+            }
+        }
+
+        private void DeletePlaylistImages()
+        {
+            try
+            {
+                var path = Path.Combine(_config.ApplicationPaths.DataPath, "playlists");
+
+                var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories)
+                    .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i) ?? string.Empty))
+                    .ToList();
+
+                foreach (var file in files)
+                {
+                    try
+                    {
+                        File.Delete(file);
+                    }
+                    catch (IOException)
+                    {
+
+                    }
+                }
+            }
+            catch (IOException)
+            {
+
+            }
+        }
+    }
+}

+ 56 - 0
MediaBrowser.Server.Startup.Common/Migrations/RenameXbmcOptions.cs

@@ -0,0 +1,56 @@
+using MediaBrowser.Controller.Configuration;
+using System;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+    public class RenameXbmcOptions
+    {
+        private readonly IServerConfigurationManager _config;
+
+        public RenameXbmcOptions(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
+        public void Run()
+        {
+            var changed = false;
+
+            foreach (var option in _config.Configuration.MetadataOptions)
+            {
+                if (Migrate(option.DisabledMetadataSavers))
+                {
+                    changed = true;
+                }
+                if (Migrate(option.LocalMetadataReaderOrder))
+                {
+                    changed = true;
+                }
+            }
+
+            if (changed)
+            {
+                _config.SaveConfiguration();
+            }
+        }
+
+        private bool Migrate(string[] options)
+        {
+            var changed = false;
+
+            if (options != null)
+            {
+                for (var i = 0; i < options.Length; i++)
+                {
+                    if (string.Equals(options[i], "Xbmc Nfo", StringComparison.OrdinalIgnoreCase))
+                    {
+                        options[i] = "Nfo";
+                        changed = true;
+                    }
+                }
+            }
+
+            return changed;
+        }
+    }
+}

+ 56 - 0
MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs

@@ -0,0 +1,56 @@
+using MediaBrowser.Controller.Configuration;
+using System;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+    public class RenameXmlOptions
+    {
+        private readonly IServerConfigurationManager _config;
+
+        public RenameXmlOptions(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
+        public void Run()
+        {
+            var changed = false;
+
+            foreach (var option in _config.Configuration.MetadataOptions)
+            {
+                if (Migrate(option.DisabledMetadataSavers))
+                {
+                    changed = true;
+                }
+                if (Migrate(option.LocalMetadataReaderOrder))
+                {
+                    changed = true;
+                }
+            }
+
+            if (changed)
+            {
+                _config.SaveConfiguration();
+            }
+        }
+
+        private bool Migrate(string[] options)
+        {
+            var changed = false;
+
+            if (options != null)
+            {
+                for (var i = 0; i < options.Length; i++)
+                {
+                    if (string.Equals(options[i], "Media Browser Xml", StringComparison.OrdinalIgnoreCase))
+                    {
+                        options[i] = "Media Browser Legacy Xml";
+                        changed = true;
+                    }
+                }
+            }
+
+            return changed;
+        }
+    }
+}

+ 29 - 0
MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs

@@ -69,6 +69,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
             Fetch(item, userDataList, metadataFile, settings, cancellationToken);
         }
 
+        protected virtual bool SupportsUrlAfterClosingXmlTag
+        {
+            get { return false; }
+        }
+
         /// <summary>
         /// Fetches the specified item.
         /// </summary>
@@ -79,6 +84,30 @@ namespace MediaBrowser.XbmcMetadata.Parsers
         /// <param name="cancellationToken">The cancellation token.</param>
         private void Fetch(T item, List<UserItemData> userDataList, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken)
         {
+            if (!SupportsUrlAfterClosingXmlTag)
+            {
+                using (var streamReader = BaseNfoSaver.GetStreamReader(metadataFile))
+                {
+                    // Use XmlReader for best performance
+                    using (var reader = XmlReader.Create(streamReader, settings))
+                    {
+                        reader.MoveToContent();
+
+                        // Loop through each element
+                        while (reader.Read())
+                        {
+                            cancellationToken.ThrowIfCancellationRequested();
+
+                            if (reader.NodeType == XmlNodeType.Element)
+                            {
+                                FetchDataFromXmlNode(reader, item, userDataList);
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+
             using (var streamReader = BaseNfoSaver.GetStreamReader(metadataFile))
             {
                 // Need to handle a url after the xml data

+ 8 - 0
MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs

@@ -28,6 +28,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers
             Fetch(item, userDataList, metadataFile, cancellationToken);
         }
 
+        protected override bool SupportsUrlAfterClosingXmlTag
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         /// <summary>
         /// Fetches the data from XML node.
         /// </summary>

+ 1 - 1
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

@@ -131,7 +131,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
         {
             get
             {
-                return "Xbmc Nfo";
+                return "Nfo";
             }
         }
 

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

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

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

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

+ 1 - 1
Nuget/MediaBrowser.Model.Signed.nuspec

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

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

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