diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index b9b7d8a2db..367d2242c6 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -33,8 +33,6 @@ namespace Emby.Common.Implementations.Net _LocalPort = localPort; _Socket.Bind(new IPEndPoint(ip, _LocalPort)); - if (_LocalPort == 0) - _LocalPort = (_Socket.LocalEndPoint as IPEndPoint).Port; } #endregion diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index f65331ec78..313db6a759 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -100,7 +100,8 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention) + string expires; + if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out expires)) { responseHeaders["Expires"] = "-1"; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index b7d2d1748d..1fe5d87cef 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -645,6 +645,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV existingTimer.SeasonNumber = updatedTimer.SeasonNumber; existingTimer.ShortOverview = updatedTimer.ShortOverview; existingTimer.StartDate = updatedTimer.StartDate; + existingTimer.ShowId = updatedTimer.ShowId; } public Task GetChannelImageAsync(string channelId, CancellationToken cancellationToken) @@ -1836,6 +1837,39 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return seriesTimer.SkipEpisodesInLibrary && IsProgramAlreadyInLibrary(timer); } + private void HandleDuplicateShowIds(List timers) + { + foreach (var timer in timers.Skip(1)) + { + // TODO: Get smarter, prefer HD, etc + + timer.Status = RecordingStatus.Cancelled; + _timerProvider.Update(timer); + } + } + + private void SearchForDuplicateShowIds(List timers) + { + var groups = timers.ToLookup(i => i.ShowId ?? string.Empty).ToList(); + + foreach (var group in groups) + { + if (string.IsNullOrWhiteSpace(group.Key)) + { + continue; + } + + var groupTimers = group.ToList(); + + if (groupTimers.Count < 2) + { + continue; + } + + HandleDuplicateShowIds(groupTimers); + } + } + private async Task UpdateTimersForSeriesTimer(List epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers) { var allTimers = GetTimersForSeries(seriesTimer, epgData) @@ -1843,6 +1877,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); + var enabledTimersForSeries = new List(); + if (registration.IsValid) { foreach (var timer in allTimers) @@ -1855,6 +1891,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { timer.Status = RecordingStatus.Cancelled; } + else + { + enabledTimersForSeries.Add(timer); + } _timerProvider.Add(timer); } else @@ -1870,6 +1910,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV existingTimer.Status = RecordingStatus.Cancelled; } + if (existingTimer.Status != RecordingStatus.Cancelled) + { + enabledTimersForSeries.Add(existingTimer); + } + existingTimer.SeriesTimerId = seriesTimer.Id; _timerProvider.Update(existingTimer); } @@ -1877,6 +1922,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } + SearchForDuplicateShowIds(enabledTimersForSeries); + if (deleteInvalidTimers) { var allTimerIds = allTimers @@ -1901,8 +1948,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private IEnumerable GetTimersForSeries(SeriesTimerInfo seriesTimer, - IEnumerable allPrograms) + private IEnumerable GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable allPrograms) { if (seriesTimer == null) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 0ae5971bcb..1b6ddc73f9 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -31,6 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV timer.Name = parent.Name; timer.Overview = parent.Overview; timer.SeriesTimerId = seriesTimer.Id; + timer.ShowId = parent.ShowId; CopyProgramInfoToTimerInfo(parent, timer); diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 33db69ee2c..45158b3c22 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -124,12 +124,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings private ProgramInfo GetProgramInfo(XmlTvProgram p, ListingsProviderInfo info) { + var episodeTitle = p.Episode == null ? null : p.Episode.Title; + var programInfo = new ProgramInfo { ChannelId = p.ChannelId, EndDate = GetDate(p.EndDate), EpisodeNumber = p.Episode == null ? null : p.Episode.Episode, - EpisodeTitle = p.Episode == null ? null : p.Episode.Title, + EpisodeTitle = episodeTitle, Genres = p.Categories, Id = String.Format("{0}_{1:O}", p.ChannelId, p.StartDate), // Construct an id from the channel and start date, StartDate = GetDate(p.StartDate), @@ -149,7 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings 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, - SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null + SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null, + ShowId = ((p.Title ?? string.Empty) + (episodeTitle ?? string.Empty)).GetMD5().ToString("N") }; if (programInfo.IsMovie) diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index fd614253ae..ee8dd5d3a7 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -34,6 +34,8 @@ namespace MediaBrowser.Controller.LiveTv /// The program identifier. public string ProgramId { get; set; } + public string ShowId { get; set; } + /// /// Name of the recording. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index cdda858b72..64225ae76c 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -154,7 +154,6 @@ namespace MediaBrowser.Model.Configuration /// /// true if [enable dashboard response caching]; otherwise, false. public bool EnableDashboardResponseCaching { get; set; } - public bool EnableDashboardResourceMinification { get; set; } /// /// Allows the dashboard to be served from a custom path. @@ -230,7 +229,6 @@ namespace MediaBrowser.Model.Configuration HttpsPortNumber = DefaultHttpsPort; EnableHttps = false; EnableDashboardResponseCaching = true; - EnableDashboardResourceMinification = true; EnableAnonymousUsageReporting = true; EnableAutomaticRestart = true; diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index dfca9e7718..bf1d4991cf 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -45,6 +45,8 @@ /// Chapters, + ChildCount, + /// /// The critic rating summary /// diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index ebd11ca9a6..7fcfbfb131 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -113,6 +113,7 @@ namespace MediaBrowser.WebDashboard.Api private readonly ILocalizationManager _localization; private readonly IJsonSerializer _jsonSerializer; private readonly IAssemblyInfo _assemblyInfo; + private readonly IMemoryStreamFactory _memoryStreamFactory; /// /// Initializes a new instance of the class. @@ -120,7 +121,7 @@ namespace MediaBrowser.WebDashboard.Api /// The app host. /// The server configuration manager. /// The file system. - public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, IAssemblyInfo assemblyInfo, ILogger logger, IHttpResultFactory resultFactory) + public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, IAssemblyInfo assemblyInfo, ILogger logger, IHttpResultFactory resultFactory, IMemoryStreamFactory memoryStreamFactory) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; @@ -130,6 +131,7 @@ namespace MediaBrowser.WebDashboard.Api _assemblyInfo = assemblyInfo; _logger = logger; _resultFactory = resultFactory; + _memoryStreamFactory = memoryStreamFactory; } /// @@ -161,7 +163,7 @@ namespace MediaBrowser.WebDashboard.Api if (plugin != null && stream != null) { - return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator().ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersion.ToString(), null, false)); + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator().ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersion.ToString(), null)); } throw new ResourceNotFoundException(); @@ -294,7 +296,7 @@ namespace MediaBrowser.WebDashboard.Api cacheDuration = TimeSpan.FromDays(365); } - var cacheKey = (_appHost.ApplicationVersion.ToString() + (localizationCulture ?? string.Empty) + path).GetMD5(); + var cacheKey = (_appHost.ApplicationVersion + (localizationCulture ?? string.Empty) + path).GetMD5(); return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path, localizationCulture)).ConfigureAwait(false); } @@ -312,15 +314,13 @@ namespace MediaBrowser.WebDashboard.Api /// Task{Stream}. private Task GetResourceStream(string path, string localizationCulture) { - var minify = _serverConfigurationManager.Configuration.EnableDashboardResourceMinification; - return GetPackageCreator() - .GetResource(path, null, localizationCulture, _appHost.ApplicationVersion.ToString(), minify); + .GetResource(path, null, localizationCulture, _appHost.ApplicationVersion.ToString()); } private PackageCreator GetPackageCreator() { - return new PackageCreator(_fileSystem, _localization, _logger, _serverConfigurationManager, _jsonSerializer); + return new PackageCreator(_fileSystem, _logger, _serverConfigurationManager, _memoryStreamFactory); } private List GetDeployIgnoreExtensions() @@ -507,7 +507,7 @@ namespace MediaBrowser.WebDashboard.Api private async Task DumpFile(string resourceVirtualPath, string destinationFilePath, string mode, string culture, string appVersion) { - using (var stream = await GetPackageCreator().GetResource(resourceVirtualPath, mode, culture, appVersion, false).ConfigureAwait(false)) + using (var stream = await GetPackageCreator().GetResource(resourceVirtualPath, mode, culture, appVersion).ConfigureAwait(false)) { using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 260352c7ea..f2df019769 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -16,32 +16,28 @@ namespace MediaBrowser.WebDashboard.Api public class PackageCreator { private readonly IFileSystem _fileSystem; - private readonly ILocalizationManager _localization; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - private readonly IJsonSerializer _jsonSerializer; + private readonly IMemoryStreamFactory _memoryStreamFactory; - public PackageCreator(IFileSystem fileSystem, ILocalizationManager localization, ILogger logger, IServerConfigurationManager config, IJsonSerializer jsonSerializer) + public PackageCreator(IFileSystem fileSystem, ILogger logger, IServerConfigurationManager config, IMemoryStreamFactory memoryStreamFactory) { _fileSystem = fileSystem; - _localization = localization; _logger = logger; _config = config; - _jsonSerializer = jsonSerializer; + _memoryStreamFactory = memoryStreamFactory; } public async Task GetResource(string path, string mode, string localizationCulture, - string appVersion, - bool enableMinification) + string appVersion) { Stream resourceStream; if (path.Equals("css/all.css", StringComparison.OrdinalIgnoreCase)) { - resourceStream = await GetAllCss(enableMinification).ConfigureAwait(false); - enableMinification = false; + resourceStream = await GetAllCss().ConfigureAwait(false); } else { @@ -56,7 +52,7 @@ namespace MediaBrowser.WebDashboard.Api { if (IsCoreHtml(path)) { - resourceStream = await ModifyHtml(path, resourceStream, mode, appVersion, localizationCulture, enableMinification).ConfigureAwait(false); + resourceStream = await ModifyHtml(path, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); } } } @@ -140,20 +136,14 @@ namespace MediaBrowser.WebDashboard.Api /// /// Modifies the HTML by adding common meta tags, css and js. /// - /// The path. - /// The source stream. - /// The mode. - /// The application version. - /// The localization culture. - /// if set to true [enable minification]. /// Task{Stream}. - public async Task ModifyHtml(string path, Stream sourceStream, string mode, string appVersion, string localizationCulture, bool enableMinification) + public async Task ModifyHtml(string path, Stream sourceStream, string mode, string appVersion, string localizationCulture) { using (sourceStream) { string html; - using (var memoryStream = new MemoryStream()) + using (var memoryStream = _memoryStreamFactory.CreateNew()) { await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false); @@ -202,7 +192,7 @@ namespace MediaBrowser.WebDashboard.Api var bytes = Encoding.UTF8.GetBytes(html); - return new MemoryStream(bytes); + return _memoryStreamFactory.CreateNew(bytes); } } @@ -332,9 +322,9 @@ namespace MediaBrowser.WebDashboard.Api /// Gets all CSS. /// /// Task{Stream}. - private async Task GetAllCss(bool enableMinification) + private async Task GetAllCss() { - var memoryStream = new MemoryStream(); + var memoryStream = _memoryStreamFactory.CreateNew(); var files = new[] {