diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 459b6dfb63..2f2968db8f 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -402,47 +402,31 @@ namespace MediaBrowser.Controller.Entities.TV public static IEnumerable FilterEpisodesBySeason(IEnumerable episodes, Season parentSeason, bool includeSpecials) { var seasonNumber = parentSeason.IndexNumber; - if (!includeSpecials || (seasonNumber.HasValue && seasonNumber.Value == 0)) + var seasonPresentationKey = parentSeason.PresentationUniqueKey; + + var supportSpecialsInSeason = includeSpecials && seasonNumber.HasValue && seasonNumber.Value != 0; + + return episodes.Where(episode => { - var seasonPresentationKey = parentSeason.PresentationUniqueKey; - - return episodes.Where(i => + var currentSeasonNumber = supportSpecialsInSeason ? episode.AiredSeasonNumber : episode.ParentIndexNumber; + if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value) { - if ((i.ParentIndexNumber ?? -1) == seasonNumber) - { - return true; - } - if (!i.ParentIndexNumber.HasValue) - { - var season = i.Season; - return season != null && string.Equals(season.PresentationUniqueKey, seasonPresentationKey, StringComparison.OrdinalIgnoreCase); - } + return true; + } - return false; - }); - } - else - { - var seasonPresentationKey = parentSeason.PresentationUniqueKey; - - return episodes.Where(episode => + if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType.Virtual) { - var currentSeasonNumber = episode.AiredSeasonNumber; + return true; + } - if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value) - { - return true; - } + if (!episode.ParentIndexNumber.HasValue) + { + var season = episode.Season; + return season != null && string.Equals(season.PresentationUniqueKey, seasonPresentationKey, StringComparison.OrdinalIgnoreCase); + } - if (!episode.ParentIndexNumber.HasValue) - { - var season = episode.Season; - return season != null && string.Equals(season.PresentationUniqueKey, seasonPresentationKey, StringComparison.OrdinalIgnoreCase); - } - - return false; - }); - } + return false; + }); } protected override bool GetBlockUnratedValue(UserPolicy config) diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs index f5048bdda0..5ecd70cc5c 100644 --- a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs +++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs @@ -15,5 +15,6 @@ namespace MediaBrowser.Controller.LiveTv Task AddMetadata(ListingsProviderInfo info, List channels, CancellationToken cancellationToken); Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings); Task> GetLineups(ListingsProviderInfo info, string country, string location); + Task> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index a4bd32fffe..15fc9350b6 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.Querying; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Events; namespace MediaBrowser.Controller.LiveTv { @@ -385,5 +386,12 @@ namespace MediaBrowser.Controller.LiveTv List GetSatIniMappings(); Task> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken); + + Task> GetChannelsFromListingsProvider(string id, CancellationToken cancellationToken); + + event EventHandler> SeriesTimerCancelled; + event EventHandler> TimerCancelled; + event EventHandler> TimerCreated; + event EventHandler> SeriesTimerCreated; } } diff --git a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs new file mode 100644 index 0000000000..0e1a054754 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.LiveTv +{ + public class TimerEventInfo + { + public string Id { get; set; } + public string ProgramId { get; set; } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4cfdc641c0..9b4c35c419 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -218,6 +218,7 @@ + diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 0e44980df2..242a2d24e4 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.LiveTv { @@ -90,5 +91,17 @@ namespace MediaBrowser.Model.LiveTv EnableAllTuners = true; ChannelMappings = new NameValuePair[] {}; } + + public string GetMappedChannel(string channelNumber) + { + foreach (NameValuePair mapping in ChannelMappings) + { + if (StringHelper.EqualsIgnoreCase(mapping.Name, channelNumber)) + { + return mapping.Value; + } + } + return channelNumber; + } } } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs new file mode 100644 index 0000000000..cc4ef1972e --- /dev/null +++ b/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Server.Implementations.EntryPoints +{ + public class RecordingNotifier : IServerEntryPoint + { + private readonly ILiveTvManager _liveTvManager; + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + private readonly ILogger _logger; + + public RecordingNotifier(ISessionManager sessionManager, IUserManager userManager, ILogger logger, ILiveTvManager liveTvManager) + { + _sessionManager = sessionManager; + _userManager = userManager; + _logger = logger; + _liveTvManager = liveTvManager; + } + + public void Run() + { + _liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled; + _liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled; + _liveTvManager.TimerCreated += _liveTvManager_TimerCreated; + _liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated; + } + + private void _liveTvManager_SeriesTimerCreated(object sender, Model.Events.GenericEventArgs e) + { + SendMessage("SeriesTimerCreated", e.Argument); + } + + private void _liveTvManager_TimerCreated(object sender, Model.Events.GenericEventArgs e) + { + SendMessage("TimerCreated", e.Argument); + } + + private void _liveTvManager_SeriesTimerCancelled(object sender, Model.Events.GenericEventArgs e) + { + SendMessage("SeriesTimerCancelled", e.Argument); + } + + private void _liveTvManager_TimerCancelled(object sender, Model.Events.GenericEventArgs e) + { + SendMessage("TimerCancelled", e.Argument); + } + + private async void SendMessage(string name, TimerEventInfo info) + { + var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).ToList(); + + foreach (var user in users) + { + try + { + await _sessionManager.SendMessageToUserSessions(user.Id.ToString("N"), name, info, CancellationToken.None); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending message", ex); + } + } + } + + public void Dispose() + { + _liveTvManager.TimerCancelled -= _liveTvManager_TimerCancelled; + _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled; + _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated; + _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated; + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 41c137c295..d3e5bc1f9e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1000,7 +1000,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV result.Item3.Release(); } - _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false); + _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); } } catch (OperationCanceledException) diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index ae2a850900..fe455665ff 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -869,6 +869,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return GetHeadends(info, country, location, CancellationToken.None); } + public async Task> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken) + { + return new List(); + } + public class ScheduleDirect { public class Token diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 328dd99792..d9b7e8f4be 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -107,11 +107,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var reader = new XmlTvReader(info.Path, GetLanguage(), null); var results = reader.GetChannels().ToList(); - if (channels != null && channels.Count > 0) + if (channels != null) { channels.ForEach(c => { - var match = results.FirstOrDefault(r => r.Id == c.Id); + var channelNumber = info.GetMappedChannel(c.Number); + var match = results.FirstOrDefault(r => string.Equals(r.Id, channelNumber, StringComparison.OrdinalIgnoreCase)); + if (match != null && match.Icon != null && !String.IsNullOrEmpty(match.Icon.Source)) { c.ImageUrl = match.Icon.Source; @@ -142,5 +144,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings // Should this method be async? return Task.FromResult(results.Select(c => new NameIdPair() { Id = c.Id, Name = c.DisplayName }).ToList()); } + + public async Task> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken) + { + return new List(); + } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index e126e5411e..46f7a8f30c 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -30,6 +30,8 @@ using System.Threading.Tasks; using CommonIO; using IniParser; using IniParser.Model; +using MediaBrowser.Common.Events; +using MediaBrowser.Model.Events; namespace MediaBrowser.Server.Implementations.LiveTv { @@ -64,6 +66,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly List _listingProviders = new List(); private readonly IFileSystem _fileSystem; + public event EventHandler> SeriesTimerCancelled; + public event EventHandler> TimerCancelled; + public event EventHandler> TimerCreated; + public event EventHandler> SeriesTimerCreated; + public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem) { _config = config; @@ -1759,6 +1766,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false); _lastRecordingRefreshTime = DateTime.MinValue; + + EventHelper.QueueEventIfNotNull(TimerCancelled, this, new GenericEventArgs + { + Argument = new TimerEventInfo + { + Id = id + } + }, _logger); } public async Task CancelSeriesTimer(string id) @@ -1774,6 +1789,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false); _lastRecordingRefreshTime = DateTime.MinValue; + + EventHelper.QueueEventIfNotNull(SeriesTimerCancelled, this, new GenericEventArgs + { + Argument = new TimerEventInfo + { + Id = id + } + }, _logger); } public async Task GetRecording(string id, DtoOptions options, CancellationToken cancellationToken, User user = null) @@ -1993,6 +2016,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false); _lastRecordingRefreshTime = DateTime.MinValue; _logger.Info("New recording scheduled"); + + EventHelper.QueueEventIfNotNull(TimerCreated, this, new GenericEventArgs + { + Argument = new TimerEventInfo + { + ProgramId = info.ProgramId + } + }, _logger); } public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken) @@ -2007,6 +2038,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false); _lastRecordingRefreshTime = DateTime.MinValue; + + EventHelper.QueueEventIfNotNull(SeriesTimerCreated, this, new GenericEventArgs + { + Argument = new TimerEventInfo + { + ProgramId = info.ProgramId + } + }, _logger); } public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken) @@ -2521,5 +2560,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv { return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken); } + + public Task> GetChannelsFromListingsProvider(string id, CancellationToken cancellationToken) + { + var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); + var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase)); + return provider.GetChannels(info, cancellationToken); + } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 52ffe3a4b4..cc78656be3 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -142,6 +142,7 @@ + diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 9acb3bea27..f344e6f1c4 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -381,7 +381,8 @@ namespace MediaBrowser.Server.Startup.Common new OmdbEpisodeProviderMigration(ServerConfigurationManager), new MovieDbEpisodeProviderMigration(ServerConfigurationManager), new DbMigration(ServerConfigurationManager, TaskManager), - new FolderViewSettingMigration(ServerConfigurationManager, UserManager) + new FolderViewSettingMigration(ServerConfigurationManager, UserManager), + new CollectionGroupingMigration(ServerConfigurationManager, UserManager) }; foreach (var task in migrations)