浏览代码

Merge pull request #1821 from MediaBrowser/dev

Dev
Luke 9 年之前
父节点
当前提交
ee9c6c5664

+ 8 - 2
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1207,7 +1207,7 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
-        private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream)
+        private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream, string outputVideoCodec)
         {
             var bitrate = request.VideoBitRate;
 
@@ -1232,6 +1232,12 @@ namespace MediaBrowser.Api.Playback
                 }
             }
 
+            if (bitrate.HasValue)
+            {
+                var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
+                bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
+            }
+
             return bitrate;
         }
 
@@ -1692,7 +1698,7 @@ namespace MediaBrowser.Api.Playback
             if (videoRequest != null)
             {
                 state.OutputVideoCodec = state.VideoRequest.VideoCodec;
-                state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream);
+                state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
 
                 if (state.OutputVideoBitrate.HasValue)
                 {

+ 1 - 1
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -221,7 +221,7 @@ namespace MediaBrowser.Api.Subtitles
 
                     if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
                     {
-                        text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+                        //text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
                     }
 
                     return ResultFactory.GetResult(text, MimeTypes.GetMimeType("file." + request.Format));

+ 7 - 2
MediaBrowser.Api/UserService.cs

@@ -385,7 +385,7 @@ namespace MediaBrowser.Api
                 throw new ResourceNotFoundException("User not found");
             }
 
-            await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
+            await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), null).ConfigureAwait(false);
 
             await _userManager.DeleteUser(user).ConfigureAwait(false);
         }
@@ -465,6 +465,10 @@ namespace MediaBrowser.Api
                 }
 
                 await _userManager.ChangePassword(user, request.NewPassword).ConfigureAwait(false);
+
+                var currentToken = AuthorizationContext.GetAuthorizationInfo(Request).Token;
+
+                await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken).ConfigureAwait(false);
             }
         }
 
@@ -602,7 +606,8 @@ namespace MediaBrowser.Api
                     throw new ArgumentException("There must be at least one enabled user in the system.");
                 }
 
-                await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
+                var currentToken = AuthorizationContext.GetAuthorizationInfo(Request).Token;
+                await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken).ConfigureAwait(false);
             }
 
             await _userManager.UpdateUserPolicy(request.Id, request).ConfigureAwait(false);

+ 2 - 2
MediaBrowser.Controller/Entities/Folder.cs

@@ -875,7 +875,7 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User))
+            if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
             {
                 Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");
                 return true;
@@ -983,7 +983,7 @@ namespace MediaBrowser.Controller.Entities
 
         protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
         {
-            return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager);
+            return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager);
         }
 
         public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)

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

@@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Entities
                 parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
             }
 
-            return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager, PlaylistManager)
+            return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager, PlaylistManager)
                 .GetUserItems(parent, this, ViewType, query);
         }
 

+ 18 - 13
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -18,6 +18,8 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Configuration;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -30,10 +32,10 @@ namespace MediaBrowser.Controller.Entities
         private readonly ILogger _logger;
         private readonly IUserDataManager _userDataManager;
         private readonly ITVSeriesManager _tvSeriesManager;
-        private readonly ICollectionManager _collectionManager;
+        private readonly IServerConfigurationManager _config;
         private readonly IPlaylistManager _playlistManager;
 
-        public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, ICollectionManager collectionManager, IPlaylistManager playlistManager)
+        public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config, IPlaylistManager playlistManager)
         {
             _userViewManager = userViewManager;
             _liveTvManager = liveTvManager;
@@ -42,7 +44,7 @@ namespace MediaBrowser.Controller.Entities
             _logger = logger;
             _userDataManager = userDataManager;
             _tvSeriesManager = tvSeriesManager;
-            _collectionManager = collectionManager;
+            _config = config;
             _playlistManager = playlistManager;
         }
 
@@ -159,7 +161,7 @@ namespace MediaBrowser.Controller.Entities
                     return await GetTvGenres(queryParent, user, query).ConfigureAwait(false);
 
                 case SpecialFolder.TvGenre:
-                    return await GetTvGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
+                    return GetTvGenreItems(queryParent, displayParent, user, query);
 
                 case SpecialFolder.TvResume:
                     return GetTvResume(queryParent, user, query);
@@ -740,7 +742,7 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(genres, parent, query);
         }
 
-        private async Task<QueryResult<BaseItem>> GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
+        private QueryResult<BaseItem> GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
             query.ParentId = queryParent.Id;
@@ -769,7 +771,7 @@ namespace MediaBrowser.Controller.Entities
         {
             items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager));
 
-            return PostFilterAndSort(items, queryParent, null, query, _libraryManager);
+            return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config);
         }
 
         public static bool FilterItem(BaseItem item, InternalItemsQuery query)
@@ -782,14 +784,15 @@ namespace MediaBrowser.Controller.Entities
             int? totalRecordLimit,
             InternalItemsQuery query)
         {
-            return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager);
+            return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config);
         }
 
         public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
             BaseItem queryParent,
             int? totalRecordLimit,
             InternalItemsQuery query,
-            ILibraryManager libraryManager)
+            ILibraryManager libraryManager,
+            IServerConfigurationManager configurationManager)
         {
             var user = query.User;
 
@@ -798,7 +801,7 @@ namespace MediaBrowser.Controller.Entities
                 query.IsVirtualUnaired,
                 query.IsUnaired);
 
-            items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user);
+            items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
 
             // This must be the last filter
             if (!string.IsNullOrEmpty(query.AdjacentTo))
@@ -812,14 +815,15 @@ namespace MediaBrowser.Controller.Entities
         public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items,
             InternalItemsQuery query,
             BaseItem queryParent,
-            User user)
+            User user,
+            IServerConfigurationManager configurationManager)
         {
             if (items == null)
             {
                 throw new ArgumentNullException("items");
             }
 
-            if (CollapseBoxSetItems(query, queryParent, user))
+            if (CollapseBoxSetItems(query, queryParent, user, configurationManager))
             {
                 items = BaseItem.CollectionManager.CollapseItemsWithinBoxSets(items, user);
             }
@@ -852,7 +856,8 @@ namespace MediaBrowser.Controller.Entities
 
         public static bool CollapseBoxSetItems(InternalItemsQuery query,
             BaseItem queryParent,
-            User user)
+            User user,
+            IServerConfigurationManager configurationManager)
         {
             // Could end up stuck in a loop like this
             if (queryParent is BoxSet)
@@ -864,7 +869,7 @@ namespace MediaBrowser.Controller.Entities
 
             if (!param.HasValue)
             {
-                if (user != null && !user.Configuration.GroupMoviesIntoBoxSets)
+                if (user != null && !configurationManager.Configuration.EnableGroupingIntoCollections)
                 {
                     return false;
                 }

+ 1 - 2
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -315,9 +315,8 @@ namespace MediaBrowser.Controller.Session
         /// <summary>
         /// Revokes the user tokens.
         /// </summary>
-        /// <param name="userId">The user identifier.</param>
         /// <returns>Task.</returns>
-        Task RevokeUserTokens(string userId);
+        Task RevokeUserTokens(string userId, string currentAccessToken);
 
         /// <summary>
         /// Revokes the token.

+ 8 - 2
MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs

@@ -99,7 +99,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             if (videoRequest != null)
             {
                 state.OutputVideoCodec = state.Options.VideoCodec;
-                state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream);
+                state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec);
 
                 if (state.OutputVideoBitrate.HasValue)
                 {
@@ -396,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return request.AudioChannels;
         }
 
-        private int? GetVideoBitrateParamValue(EncodingJobOptions request, MediaStream videoStream)
+        private int? GetVideoBitrateParamValue(EncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
         {
             var bitrate = request.VideoBitRate;
 
@@ -421,6 +421,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 }
             }
 
+            if (bitrate.HasValue)
+            {
+                var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
+                bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
+            }
+
             return bitrate;
         }
 

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

@@ -199,6 +199,7 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableStandaloneMusicKeys { get; set; }
         public bool EnableLocalizedGuids { get; set; }
         public bool EnableFolderView { get; set; }
+        public bool EnableGroupingIntoCollections { get; set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.

+ 20 - 0
MediaBrowser.Model/Dlna/ResolutionNormalizer.cs

@@ -56,5 +56,25 @@ namespace MediaBrowser.Model.Dlna
                 MaxHeight = maxHeight
             };
         }
+
+        private static double GetVideoBitrateScaleFactor(string codec)
+        {
+            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
+            {
+                return .5;
+            }
+            return 1;
+        }
+
+        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
+        {
+            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
+            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
+            var scaleFactor = outputScaleFactor/inputScaleFactor;
+            var newBitrate = scaleFactor*bitrate;
+
+            return Convert.ToInt32(newBitrate);
+        }
     }
 }

+ 11 - 6
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Model.LiveTv
 {
@@ -73,17 +74,21 @@ namespace MediaBrowser.Model.LiveTv
 
         public string[] EnabledTuners { get; set; }
         public bool EnableAllTuners { get; set; }
-        public string[] NewsGenres { get; set; }
-        public string[] SportsGenres { get; set; }
-        public string[] KidsGenres { get; set; }
+        public string[] NewsCategories { get; set; }
+        public string[] SportsCategories { get; set; }
+        public string[] KidsCategories { get; set; }
+        public string[] MovieCategories { get; set; }
+        public NameValuePair[] ChannelMappings { get; set; }
 
         public ListingsProviderInfo()
         {
-            NewsGenres = new string[] { "news" };
-            SportsGenres = new string[] { "sports", "basketball", "baseball", "football" };
-            KidsGenres = new string[] { "kids", "family", "children" };
+            NewsCategories = new string[] { "news", "journalism", "documentary", "current affairs" };
+            SportsCategories = new string[] { "sports", "basketball", "baseball", "football" };
+            KidsCategories = new string[] { "kids", "family", "children", "childrens", "disney" };
+            MovieCategories = new string[] { "movie" };
             EnabledTuners = new string[] { };
             EnableAllTuners = true;
+            ChannelMappings = new NameValuePair[] {};
         }
     }
 }

+ 0 - 1
MediaBrowser.Server.Implementations/Connect/Responses.cs

@@ -60,7 +60,6 @@ namespace MediaBrowser.Server.Implementations.Connect
         {
             return new ConnectUserPreferences
             {
-                GroupMoviesIntoBoxSets = config.GroupMoviesIntoBoxSets,
                 PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
                 SubtitleMode = config.SubtitleMode,
                 PreferredAudioLanguages = string.IsNullOrWhiteSpace(config.AudioLanguagePreference) ? new string[] { } : new[] { config.AudioLanguagePreference },

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs

@@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
 
-            if (service != null)
+            if (service != null && !item.HasImage(ImageType.Primary))
             {
                 try
                 {

+ 22 - 1
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -625,7 +625,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
                 _logger.Debug("Getting programs for channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
 
-                var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channel.Number, channel.Name, startDateUtc, endDateUtc, cancellationToken)
+                var channelMappings = GetChannelMappings(provider.Item2);
+                var channelNumber = channel.Number;
+                string mappedChannelNumber;
+                if (channelMappings.TryGetValue(channelNumber, out mappedChannelNumber))
+                {
+                    _logger.Debug("Found mapped channel on provider {0}. Tuner channel number: {1}, Mapped channel number: {2}", provider.Item1.Name, channelNumber, mappedChannelNumber);
+                    channelNumber = mappedChannelNumber;
+                }
+
+                var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken)
                         .ConfigureAwait(false);
 
                 var list = programs.ToList();
@@ -647,6 +656,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return new List<ProgramInfo>();
         }
 
+        private Dictionary<string, string> GetChannelMappings(ListingsProviderInfo info)
+        {
+            var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+            foreach (var mapping in info.ChannelMappings)
+            {
+                dict[mapping.Name] = mapping.Value;
+            }
+
+            return dict;
+        }
+
         private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
         {
             return GetConfiguration().ListingProviders

+ 0 - 44
MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs

@@ -1,44 +0,0 @@
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.LiveTv;
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Server.Implementations.LiveTv.Listings
-{
-    public class XmlTv : IListingsProvider
-    {
-        public string Name
-        {
-            get { return "XmlTV"; }
-        }
-
-        public string Type
-        {
-            get { return "xmltv"; }
-        }
-
-        public Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
-
-        public async Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken)
-        {
-            // Might not be needed
-        }
-
-        public async Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings)
-        {
-            // Check that the path or url is valid. If not, throw a file not found exception
-        }
-
-        public Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location)
-        {
-            // In theory this should never be called because there is always only one lineup
-            throw new NotImplementedException();
-        }
-    }
-}

+ 112 - 0
MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs

@@ -0,0 +1,112 @@
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Emby.XmlTv.Classes;
+using MediaBrowser.Controller.Configuration;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.Listings
+{
+    public class XmlTvListingsProvider : IListingsProvider
+    {
+        private readonly IServerConfigurationManager _config;
+
+        public XmlTvListingsProvider(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
+        public string Name
+        {
+            get { return "XmlTV"; }
+        }
+
+        public string Type
+        {
+            get { return "xmltv"; }
+        }
+
+        private string GetLanguage()
+        {
+            return _config.Configuration.PreferredMetadataLanguage;
+        }
+
+        // TODO: Should this method be async?
+        public Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
+        {
+            var reader = new XmlTvReader(info.Path, GetLanguage(), null);
+            string mappedChannel = channelNumber;
+
+            var results = reader.GetProgrammes(mappedChannel, startDateUtc, endDateUtc, cancellationToken);
+            return Task.FromResult(results.Select(p => new ProgramInfo()
+            {
+                ChannelId = p.ChannelId,
+                EndDate = p.EndDate,
+                EpisodeNumber = p.Episode == null ? null : p.Episode.Episode,
+                EpisodeTitle = p.Episode == null ? null : p.Episode.Title,
+                Genres = p.Categories,
+                Id = String.Format("{0}_{1:O}", p.ChannelId, p.StartDate), // Construct an id from the channel and start date,
+                StartDate = p.StartDate,
+                Name = p.Title,
+                Overview = p.Description,
+                ShortOverview = p.Description,
+                ProductionYear = !p.CopyrightDate.HasValue ? (int?)null : p.CopyrightDate.Value.Year,
+                SeasonNumber = p.Episode == null ? null : p.Episode.Series,
+                IsSeries = p.IsSeries,
+                IsRepeat = p.IsRepeat,
+                // IsPremiere = !p.PreviouslyShown.HasValue,
+                IsKids = p.Categories.Any(info.KidsCategories.Contains),
+                IsMovie = p.Categories.Any(info.MovieCategories.Contains),
+                IsNews = p.Categories.Any(info.NewsCategories.Contains),
+                IsSports = p.Categories.Any(info.SportsCategories.Contains),
+                ImageUrl = p.Icon != null && !String.IsNullOrEmpty(p.Icon.Source) ? p.Icon.Source : null,
+                HasImage = p.Icon != null && !String.IsNullOrEmpty(p.Icon.Source),
+                OfficialRating = p.Rating != null && !String.IsNullOrEmpty(p.Rating.Value) ? p.Rating.Value : null,
+                CommunityRating = p.StarRating.HasValue ? p.StarRating.Value : (float?)null
+            }));
+        }
+
+        public async Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken)
+        {
+            // Add the channel image url
+            var reader = new XmlTvReader(info.Path, GetLanguage(), null);
+            var results = reader.GetChannels().ToList();
+
+            if (channels != null && channels.Count > 0)
+	        {
+                channels.ForEach(c => {
+                    var match = results.FirstOrDefault(r => r.Id == c.Id);
+                    if (match != null && match.Icon != null && !String.IsNullOrEmpty(match.Icon.Source))
+                    {
+                        c.ImageUrl = match.Icon.Source;
+                    }
+                });
+	        }
+        }
+
+        public async Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings)
+        {
+            // Check that the path or url is valid. If not, throw a file not found exception
+            if (!File.Exists(info.Path))
+            {
+                throw new FileNotFoundException("Could not find the XmlTv file specified:", info.Path);
+            }
+        }
+
+        public Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location)
+        {
+            // In theory this should never be called because there is always only one lineup
+            var reader = new XmlTvReader(info.Path, GetLanguage(), null);
+            var results = reader.GetChannels();
+
+            // Should this method be async?
+            return Task.FromResult(results.Select(c => new NameIdPair() { Id = c.Id, Name = c.DisplayName }).ToList());
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -233,7 +233,7 @@
     <Compile Include="LiveTv\EmbyTV\SeriesTimerManager.cs" />
     <Compile Include="LiveTv\EmbyTV\TimerManager.cs" />
     <Compile Include="LiveTv\Listings\SchedulesDirect.cs" />
-    <Compile Include="LiveTv\Listings\XmlTv.cs" />
+    <Compile Include="LiveTv\Listings\XmlTvListingsProvider.cs" />
     <Compile Include="LiveTv\LiveTvConfigurationFactory.cs" />
     <Compile Include="LiveTv\LiveTvDtoService.cs" />
     <Compile Include="LiveTv\LiveTvManager.cs" />

+ 5 - 2
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -1451,7 +1451,7 @@ namespace MediaBrowser.Server.Implementations.Session
             }
         }
 
-        public async Task RevokeUserTokens(string userId)
+        public async Task RevokeUserTokens(string userId, string currentAccessToken)
         {
             var existing = _authRepo.Get(new AuthenticationInfoQuery
             {
@@ -1461,7 +1461,10 @@ namespace MediaBrowser.Server.Implementations.Session
 
             foreach (var info in existing.Items)
             {
-                await Logout(info.AccessToken).ConfigureAwait(false);
+                if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase))
+                {
+                    await Logout(info.AccessToken).ConfigureAwait(false);
+                }
             }
         }
 

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

@@ -71,6 +71,7 @@
     <Compile Include="FFMpeg\FFmpegValidator.cs" />
     <Compile Include="INativeApp.cs" />
     <Compile Include="MbLinkShortcutHandler.cs" />
+    <Compile Include="Migrations\CollectionGroupingMigration.cs" />
     <Compile Include="Migrations\FolderViewSettingMigration.cs" />
     <Compile Include="Migrations\IVersionMigration.cs" />
     <Compile Include="Migrations\DbMigration.cs" />

+ 44 - 0
MediaBrowser.Server.Startup.Common/Migrations/CollectionGroupingMigration.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+    public class CollectionGroupingMigration : IVersionMigration
+    {
+        private readonly IServerConfigurationManager _config;
+        private readonly IUserManager _userManager;
+
+        public CollectionGroupingMigration(IServerConfigurationManager config, IUserManager userManager)
+        {
+            _config = config;
+            _userManager = userManager;
+        }
+
+        public void Run()
+        {
+            var migrationKey = this.GetType().Name;
+            var migrationKeyList = _config.Configuration.Migrations.ToList();
+
+            if (!migrationKeyList.Contains(migrationKey))
+            {
+                if (_config.Configuration.IsStartupWizardCompleted)
+                {
+                    if (_userManager.Users.Any(i => i.Configuration.GroupMoviesIntoBoxSets))
+                    {
+                        _config.Configuration.EnableGroupingIntoCollections = true;
+                    }
+                }
+
+                migrationKeyList.Add(migrationKey);
+                _config.Configuration.Migrations = migrationKeyList.ToArray();
+                _config.SaveConfiguration();
+            }
+
+        }
+    }
+}

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

@@ -152,9 +152,6 @@
     <Content Include="dashboard-ui\css\images\logo.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\css\polymer\paper-icon-button-light.css">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\devices\windowsphone\wp.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>