diff --git a/Emby.Server.Implementations/Connect/ConnectManager.cs b/Emby.Server.Implementations/Connect/ConnectManager.cs index 7e6755f6aa..8aac2a8c47 100644 --- a/Emby.Server.Implementations/Connect/ConnectManager.cs +++ b/Emby.Server.Implementations/Connect/ConnectManager.cs @@ -995,7 +995,7 @@ namespace Emby.Server.Implementations.Connect if (changed) { - await _providerManager.SaveImage(user, imageUrl, null, ImageType.Primary, null, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(user, imageUrl, ImageType.Primary, null, CancellationToken.None).ConfigureAwait(false); await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem) { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 372a998536..de3a1664e7 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2760,7 +2760,6 @@ namespace Emby.Server.Implementations.Library return ItemRepository.UpdatePeople(item.Id, people); } - private readonly SemaphoreSlim _dynamicImageResourcePool = new SemaphoreSlim(1, 1); public async Task ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex) { foreach (var url in image.Path.Split('|')) @@ -2769,7 +2768,7 @@ namespace Emby.Server.Implementations.Library { _logger.Debug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url); - await _providerManagerFactory().SaveImage(item, url, _dynamicImageResourcePool, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false); + await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false); var newImage = item.GetImageInfo(image.Type, imageIndex); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index f942597540..89ef87c8ef 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -393,7 +393,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { try { - await provider.Item1.AddMetadata(provider.Item2, enabledChannels, cancellationToken).ConfigureAwait(false); + await AddMetadata(provider.Item1, provider.Item2, enabledChannels, enableCache, cancellationToken).ConfigureAwait(false); } catch (NotSupportedException) { @@ -409,6 +409,120 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return list; } + private async Task AddMetadata(IListingsProvider provider, ListingsProviderInfo info, List tunerChannels, bool enableCache, CancellationToken cancellationToken) + { + var epgChannels = await GetEpgChannels(provider, info, enableCache, cancellationToken).ConfigureAwait(false); + + foreach (var tunerChannel in tunerChannels) + { + var epgChannel = GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels); + + if (epgChannel != null) + { + if (!string.IsNullOrWhiteSpace(epgChannel.Name)) + { + tunerChannel.Name = epgChannel.Name; + } + } + } + } + + private readonly ConcurrentDictionary> _epgChannels = + new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); + + private async Task> GetEpgChannels(IListingsProvider provider, ListingsProviderInfo info, bool enableCache, CancellationToken cancellationToken) + { + List result; + if (!enableCache || !_epgChannels.TryGetValue(info.Id, out result)) + { + result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false); + + _epgChannels.AddOrUpdate(info.Id, result, (k, v) => result); + } + + return result; + } + + private async Task GetEpgChannelFromTunerChannel(IListingsProvider provider, ListingsProviderInfo info, ChannelInfo tunerChannel, CancellationToken cancellationToken) + { + var epgChannels = await GetEpgChannels(provider, info, true, cancellationToken).ConfigureAwait(false); + + return GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels); + } + + private string GetMappedChannel(string channelId, List mappings) + { + foreach (NameValuePair mapping in mappings) + { + if (StringHelper.EqualsIgnoreCase(mapping.Name, channelId)) + { + return mapping.Value; + } + } + return channelId; + } + + private ChannelInfo GetEpgChannelFromTunerChannel(ListingsProviderInfo info, ChannelInfo tunerChannel, List epgChannels) + { + return GetEpgChannelFromTunerChannel(info.ChannelMappings.ToList(), tunerChannel, epgChannels); + } + + public ChannelInfo GetEpgChannelFromTunerChannel(List mappings, ChannelInfo tunerChannel, List epgChannels) + { + if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId)) + { + var tunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings); + + if (string.IsNullOrWhiteSpace(tunerChannelId)) + { + tunerChannelId = tunerChannel.TunerChannelId; + } + + var channel = epgChannels.FirstOrDefault(i => string.Equals(tunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase)); + + if (channel != null) + { + return channel; + } + } + + if (!string.IsNullOrWhiteSpace(tunerChannel.Number)) + { + var tunerChannelNumber = GetMappedChannel(tunerChannel.Number, mappings); + + if (string.IsNullOrWhiteSpace(tunerChannelNumber)) + { + tunerChannelNumber = tunerChannel.Number; + } + + var channel = epgChannels.FirstOrDefault(i => string.Equals(tunerChannelNumber, i.Number, StringComparison.OrdinalIgnoreCase)); + + if (channel != null) + { + return channel; + } + } + + if (!string.IsNullOrWhiteSpace(tunerChannel.Name)) + { + var normalizedName = NormalizeName(tunerChannel.Name); + + var channel = epgChannels.FirstOrDefault(i => string.Equals(normalizedName, NormalizeName(i.Name ?? string.Empty), StringComparison.OrdinalIgnoreCase)); + + if (channel != null) + { + return channel; + } + } + + return null; + } + + private string NormalizeName(string value) + { + return value.Replace(" ", string.Empty).Replace("-", string.Empty); + } + public async Task> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken) { var list = new List(); @@ -845,54 +959,37 @@ namespace Emby.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 channelMappings = GetChannelMappings(provider.Item2); - var channelNumber = channel.Number; - var tunerChannelId = channel.TunerChannelId; + var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false); + + List programs; - if (!string.IsNullOrWhiteSpace(channelNumber)) + if (epgChannel == null) { - 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; - } + programs = new List(); + } + else + { + programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken) + .ConfigureAwait(false)).ToList(); } - - var programs = await provider.Item1.GetProgramsAsync(provider.Item2, tunerChannelId, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken) - .ConfigureAwait(false); - - var list = programs.ToList(); // Replace the value that came from the provider with a normalized value - foreach (var program in list) + foreach (var program in programs) { program.ChannelId = channelId; } - if (list.Count > 0) + if (programs.Count > 0) { - SaveEpgDataForChannel(channelId, list); + SaveEpgDataForChannel(channelId, programs); - return list; + return programs; } } return new List(); } - private Dictionary GetChannelMappings(ListingsProviderInfo info) - { - var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var mapping in info.ChannelMappings) - { - dict[mapping.Name] = mapping.Value; - } - - return dict; - } - private List> GetListingProviders() { return GetConfiguration().ListingProviders diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index e5790b875a..1b7a1c8c62 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -15,6 +15,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.LiveTv.Listings { @@ -60,8 +61,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings return dates; } - public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) + public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(channelId)) + { + throw new ArgumentNullException("channelId"); + } + + // Normalize incoming input + channelId = channelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I'); + List programsInfo = new List(); var token = await GetToken(info, cancellationToken).ConfigureAwait(false); @@ -80,15 +89,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings var dates = GetScheduleRequestDates(startDateUtc, endDateUtc); - ScheduleDirect.Station station = GetStation(info.ListingsId, channelNumber, channelName); - - if (station == null) - { - _logger.Info("No Schedules Direct Station found for channel {0} with name {1}", channelNumber, channelName); - return programsInfo; - } - - string stationID = station.stationID; + string stationID = channelId; _logger.Info("Channel Station ID is: " + stationID); List requestList = @@ -122,7 +123,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings StreamReader reader = new StreamReader(response.Content); string responseString = reader.ReadToEnd(); var dailySchedules = _jsonSerializer.DeserializeFromString>(responseString); - _logger.Debug("Found " + dailySchedules.Count + " programs on " + channelNumber + " ScheduleDirect"); + _logger.Debug("Found " + dailySchedules.Count + " programs on " + stationID + " ScheduleDirect"); httpOptions = new HttpRequestOptions() { @@ -180,7 +181,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - programsInfo.Add(GetProgram(channelNumber, schedule, programDict[schedule.programID])); + programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID])); } } } @@ -202,183 +203,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings return 0; } - private readonly object _channelCacheLock = new object(); - private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName) + private string GetChannelNumber(ScheduleDirect.Map map) { - lock (_channelCacheLock) - { - Dictionary channelPair; - if (_channelPairingCache.TryGetValue(listingsId, out channelPair)) - { - ScheduleDirect.Station station; - - if (!string.IsNullOrWhiteSpace(channelNumber) && channelPair.TryGetValue(channelNumber, out station)) - { - return station; - } - - if (!string.IsNullOrWhiteSpace(channelName)) - { - channelName = NormalizeName(channelName); - - var result = channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase)); - - if (result != null) - { - return result; - } - } + var channelNumber = map.logicalChannelNumber; - if (!string.IsNullOrWhiteSpace(channelNumber)) - { - return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase)); - } - } - - return null; - } - } - - private void AddToChannelPairCache(string listingsId, string channelNumber, ScheduleDirect.Station schChannel) - { - lock (_channelCacheLock) - { - Dictionary cache; - if (_channelPairingCache.TryGetValue(listingsId, out cache)) - { - cache[channelNumber] = schChannel; - } - else - { - cache = new Dictionary(); - cache[channelNumber] = schChannel; - _channelPairingCache[listingsId] = cache; - } - } - } - - private void ClearPairCache(string listingsId) - { - lock (_channelCacheLock) + if (string.IsNullOrWhiteSpace(channelNumber)) { - Dictionary cache; - if (_channelPairingCache.TryGetValue(listingsId, out cache)) - { - cache.Clear(); - } + channelNumber = map.channel; } - } - - private int GetChannelPairCacheCount(string listingsId) - { - lock (_channelCacheLock) + if (string.IsNullOrWhiteSpace(channelNumber)) { - Dictionary cache; - if (_channelPairingCache.TryGetValue(listingsId, out cache)) - { - return cache.Count; - } - - return 0; + channelNumber = map.atscMajor + "." + map.atscMinor; } - } + channelNumber = channelNumber.TrimStart('0'); - private string NormalizeName(string value) - { - return value.Replace(" ", string.Empty).Replace("-", string.Empty); + return channelNumber; } - public async Task AddMetadata(ListingsProviderInfo info, List channels, - CancellationToken cancellationToken) - { - var listingsId = info.ListingsId; - if (string.IsNullOrWhiteSpace(listingsId)) - { - throw new Exception("ListingsId required"); - } - - var token = await GetToken(info, cancellationToken); - - if (string.IsNullOrWhiteSpace(token)) - { - throw new Exception("token required"); - } - - ClearPairCache(listingsId); - - var httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/lineups/" + listingsId, - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 - }; - - httpOptions.RequestHeaders["token"] = token; - - using (var response = await Get(httpOptions, true, info).ConfigureAwait(false)) - { - var root = _jsonSerializer.DeserializeFromStream(response); - - foreach (ScheduleDirect.Map map in root.map) - { - var channelNumber = map.logicalChannelNumber; - - if (string.IsNullOrWhiteSpace(channelNumber)) - { - channelNumber = map.channel; - } - if (string.IsNullOrWhiteSpace(channelNumber)) - { - channelNumber = map.atscMajor + "." + map.atscMinor; - } - channelNumber = channelNumber.TrimStart('0'); - - _logger.Debug("Found channel: " + channelNumber + " in Schedules Direct"); - - var schChannel = (root.stations ?? new List()).FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); - if (schChannel != null) - { - AddToChannelPairCache(listingsId, channelNumber, schChannel); - } - else - { - AddToChannelPairCache(listingsId, channelNumber, new ScheduleDirect.Station - { - stationID = map.stationID - }); - } - } - - foreach (ChannelInfo channel in channels) - { - var station = GetStation(listingsId, channel.Number, channel.Name); - - if (station != null) - { - if (station.logo != null) - { - channel.ImageUrl = station.logo.URL; - channel.HasImage = true; - } - - if (!string.IsNullOrWhiteSpace(station.name)) - { - channel.Name = station.name; - } - } - else - { - _logger.Info("Schedules Direct doesnt have data for channel: " + channel.Number + " " + channel.Name); - } - } - } - } - - private ProgramInfo GetProgram(string channel, ScheduleDirect.Program programInfo, - ScheduleDirect.ProgramDetails details) + private ProgramInfo GetProgram(string channelId, ScheduleDirect.Program programInfo, ScheduleDirect.ProgramDetails details) { //_logger.Debug("Show type is: " + (details.showType ?? "No ShowType")); DateTime startAt = GetDate(programInfo.airDateTime); @@ -386,7 +228,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings ProgramAudio audioType = ProgramAudio.Stereo; bool repeat = programInfo.@new == null; - string newID = programInfo.programID + "T" + startAt.Ticks + "C" + channel; + string newID = programInfo.programID + "T" + startAt.Ticks + "C" + channelId; if (programInfo.audioProperties != null) { @@ -422,7 +264,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings var info = new ProgramInfo { - ChannelId = channel, + ChannelId = channelId, Id = newID, StartDate = startAt, EndDate = endAt, @@ -969,8 +811,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new Exception("ListingsId required"); } - await AddMetadata(info, new List(), cancellationToken).ConfigureAwait(false); - var token = await GetToken(info, cancellationToken); if (string.IsNullOrWhiteSpace(token)) @@ -997,39 +837,81 @@ namespace Emby.Server.Implementations.LiveTv.Listings var root = _jsonSerializer.DeserializeFromStream(response); _logger.Info("Found " + root.map.Count + " channels on the lineup on ScheduleDirect"); _logger.Info("Mapping Stations to Channel"); + + var allStations = root.stations ?? new List(); + foreach (ScheduleDirect.Map map in root.map) { - var channelNumber = map.logicalChannelNumber; - - if (string.IsNullOrWhiteSpace(channelNumber)) + var channelNumber = GetChannelNumber(map); + + var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); + if (station == null) { - channelNumber = map.channel; - } - if (string.IsNullOrWhiteSpace(channelNumber)) - { - channelNumber = map.atscMajor + "." + map.atscMinor; + station = new ScheduleDirect.Station + { + stationID = map.stationID + }; } - channelNumber = channelNumber.TrimStart('0'); var name = channelNumber; - var station = GetStation(listingsId, channelNumber, null); - if (station != null && !string.IsNullOrWhiteSpace(station.name)) - { - name = station.name; - } - - list.Add(new ChannelInfo + var channelInfo = new ChannelInfo { Number = channelNumber, Name = name - }); + }; + + if (station != null) + { + if (!string.IsNullOrWhiteSpace(station.name)) + { + channelInfo.Name = station.name; + } + + channelInfo.Id = station.stationID; + channelInfo.CallSign = station.callsign; + + if (station.logo != null) + { + channelInfo.ImageUrl = station.logo.URL; + channelInfo.HasImage = true; + } + } + + list.Add(channelInfo); } } return list; } + private ScheduleDirect.Station GetStation(List allStations, string channelNumber, string channelName) + { + if (!string.IsNullOrWhiteSpace(channelName)) + { + channelName = NormalizeName(channelName); + + var result = allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase)); + + if (result != null) + { + return result; + } + } + + if (!string.IsNullOrWhiteSpace(channelNumber)) + { + return allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase)); + } + + return null; + } + + private string NormalizeName(string value) + { + return value.Replace(" ", string.Empty).Replace("-", string.Empty); + } + public class ScheduleDirect { public class Token diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index adb4f359eb..d7803f9e35 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -106,8 +106,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings return cacheFile; } - public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) + public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(channelId)) + { + throw new ArgumentNullException("channelId"); + } + if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false)) { var length = endDateUtc - startDateUtc; @@ -120,7 +125,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); var reader = new XmlTvReader(path, GetLanguage()); - var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken); + var results = reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken); return results.Select(p => GetProgramInfo(p, info)); } @@ -194,28 +199,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings return date; } - public async Task AddMetadata(ListingsProviderInfo info, List channels, CancellationToken cancellationToken) - { - // Add the channel image url - var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); - var reader = new XmlTvReader(path, GetLanguage()); - var results = reader.GetChannels().ToList(); - - if (channels != null) - { - foreach (var c in channels) - { - 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; - } - } - } - } - public Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings) { // Assume all urls are valid. check files for existence diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 9f0d3a2250..e59a8f93c0 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2884,20 +2884,20 @@ namespace Emby.Server.Implementations.LiveTv _taskManager.CancelIfRunningAndQueue(); } - public async Task SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber) + public async Task SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId) { var config = GetConfiguration(); var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase)); - listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray(); + listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelId, StringComparison.OrdinalIgnoreCase)).ToArray(); - if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(tunerChannelId, providerChannelId, StringComparison.OrdinalIgnoreCase)) { var list = listingsProviderInfo.ChannelMappings.ToList(); list.Add(new NameValuePair { - Name = tunerChannelNumber, - Value = providerChannelNumber + Name = tunerChannelId, + Value = providerChannelId }); listingsProviderInfo.ChannelMappings = list.ToArray(); } @@ -2917,31 +2917,33 @@ namespace Emby.Server.Implementations.LiveTv _taskManager.CancelIfRunningAndQueue(); - return tunerChannelMappings.First(i => string.Equals(i.Number, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)); + return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase)); } - public TunerChannelMapping GetTunerChannelMapping(ChannelInfo channel, List mappings, List providerChannels) + public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, List mappings, List epgChannels) { var result = new TunerChannelMapping { - Name = channel.Number + " " + channel.Name, - Number = channel.Number + Name = tunerChannel.Name, + Id = tunerChannel.TunerChannelId }; - var mapping = mappings.FirstOrDefault(i => string.Equals(i.Name, channel.Number, StringComparison.OrdinalIgnoreCase)); - var providerChannelNumber = channel.Number; + if (!string.IsNullOrWhiteSpace(tunerChannel.Number)) + { + result.Name = tunerChannel.Number + " " + result.Name; + } - if (mapping != null) + if (string.IsNullOrWhiteSpace(result.Id)) { - providerChannelNumber = mapping.Value; + result.Id = tunerChannel.Id; } - var providerChannel = providerChannels.FirstOrDefault(i => string.Equals(i.Number, providerChannelNumber, StringComparison.OrdinalIgnoreCase)); + var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, epgChannels); if (providerChannel != null) { - result.ProviderChannelNumber = providerChannel.Number; result.ProviderChannelName = providerChannel.Name; + result.ProviderChannelId = providerChannel.Id; } return result; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 088c264f04..34d0dd8535 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -113,17 +113,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - var startingNumber = 1; - foreach (var channel in channels) - { - if (!string.IsNullOrWhiteSpace(channel.Number)) - { - continue; - } - - channel.Number = startingNumber.ToString(CultureInfo.InvariantCulture); - startingNumber++; - } return channels; } @@ -147,10 +136,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.Name = GetChannelName(extInf, attributes); channel.Number = GetChannelNumber(extInf, attributes, mediaUrl); - if (attributes.TryGetValue("tvg-id", out value)) + var channelId = GetTunerChannelId(attributes); + if (!string.IsNullOrWhiteSpace(channelId)) { - channel.Id = value; - channel.TunerChannelId = value; + channel.Id = channelId; + channel.TunerChannelId = channelId; } return channel; @@ -186,9 +176,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts numberString = numberString.Trim(); } - if (string.IsNullOrWhiteSpace(numberString) || - string.Equals(numberString, "-1", StringComparison.OrdinalIgnoreCase) || - string.Equals(numberString, "0", StringComparison.OrdinalIgnoreCase)) + if (!IsValidChannelNumber(numberString)) { string value; if (attributes.TryGetValue("tvg-id", out value)) @@ -206,9 +194,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts numberString = numberString.Trim(); } - if (string.IsNullOrWhiteSpace(numberString) || - string.Equals(numberString, "-1", StringComparison.OrdinalIgnoreCase) || - string.Equals(numberString, "0", StringComparison.OrdinalIgnoreCase)) + if (!IsValidChannelNumber(numberString)) { string value; if (attributes.TryGetValue("channel-id", out value)) @@ -222,9 +208,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts numberString = numberString.Trim(); } - if (string.IsNullOrWhiteSpace(numberString) || - string.Equals(numberString, "-1", StringComparison.OrdinalIgnoreCase) || - string.Equals(numberString, "0", StringComparison.OrdinalIgnoreCase)) + if (!IsValidChannelNumber(numberString)) { numberString = null; } @@ -239,8 +223,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { numberString = Path.GetFileNameWithoutExtension(mediaUrl.Split('/').Last()); - double value; - if (!double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + if (!IsValidChannelNumber(numberString)) { numberString = null; } @@ -250,6 +233,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return numberString; } + private bool IsValidChannelNumber(string numberString) + { + if (string.IsNullOrWhiteSpace(numberString) || + string.Equals(numberString, "-1", StringComparison.OrdinalIgnoreCase) || + string.Equals(numberString, "0", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + double value; + if (!double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + return false; + } + + return true; + } + private string GetChannelName(string extInf, Dictionary attributes) { var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); @@ -295,6 +296,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return name; } + private string GetTunerChannelId(Dictionary attributes) + { + string result; + attributes.TryGetValue("tvg-id", out result); + + if (string.IsNullOrWhiteSpace(result)) + { + attributes.TryGetValue("channel-id", out result); + } + + return result; + } + private Dictionary ParseExtInf(string line, out string remaining) { var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/Migrations/GuideMigration.cs b/Emby.Server.Implementations/Migrations/GuideMigration.cs index 71286b2828..99b2942dcb 100644 --- a/Emby.Server.Implementations/Migrations/GuideMigration.cs +++ b/Emby.Server.Implementations/Migrations/GuideMigration.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Migrations public async Task Run() { - var name = "GuideRefresh2"; + var name = "GuideRefresh3"; if (!_config.Configuration.Migrations.Contains(name, StringComparer.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index eb871746d7..d7ccf8f6d0 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -210,7 +210,7 @@ namespace MediaBrowser.Api.Images /// Task. private async Task DownloadRemoteImage(BaseItem item, BaseDownloadRemoteImage request) { - await _providerManager.SaveImage(item, request.ImageUrl, null, request.Type, null, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(item, request.ImageUrl, request.Type, null, CancellationToken.None).ConfigureAwait(false); await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 85a66080df..04983553b4 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -640,8 +640,8 @@ namespace MediaBrowser.Api.LiveTv { [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] public string ProviderId { get; set; } - public string TunerChannelNumber { get; set; } - public string ProviderChannelNumber { get; set; } + public string TunerChannelId { get; set; } + public string ProviderChannelId { get; set; } } public class ChannelMappingOptions @@ -765,7 +765,7 @@ namespace MediaBrowser.Api.LiveTv public async Task Post(SetChannelMapping request) { - return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelNumber, request.ProviderChannelNumber).ConfigureAwait(false); + return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelId, request.ProviderChannelId).ConfigureAwait(false); } public async Task Get(GetChannelMappingOptions request) @@ -791,7 +791,7 @@ namespace MediaBrowser.Api.LiveTv ProviderChannels = providerChannels.Select(i => new NameIdPair { Name = i.Name, - Id = i.Number + Id = i.TunerChannelId }).ToList(), diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index a880b6d778..6e0f4ada99 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -788,7 +788,7 @@ namespace MediaBrowser.Controller.Entities query.IsVirtualUnaired, query.IsUnaired); - if (collapseBoxSetItems) + if (collapseBoxSetItems && user != null) { items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager); } @@ -1119,13 +1119,11 @@ namespace MediaBrowser.Controller.Entities InternalItemsQuery query, ILibraryManager libraryManager, bool enableSorting) { - var user = query.User; - items = items.DistinctBy(i => i.GetPresentationUniqueKey(), StringComparer.OrdinalIgnoreCase); if (query.SortBy.Length > 0) { - items = libraryManager.Sort(items, user, query.SortBy, query.SortOrder); + items = libraryManager.Sort(items, query.User, query.SortBy, query.SortOrder); } var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray(); diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index fae9076588..6682942ad8 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -27,6 +27,8 @@ namespace MediaBrowser.Controller.LiveTv public string TunerChannelId { get; set; } + public string CallSign { get; set; } + /// /// Gets or sets the tuner host identifier. /// diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs index 3d610544ee..faf4a34dfa 100644 --- a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs +++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs @@ -11,8 +11,7 @@ namespace MediaBrowser.Controller.LiveTv { string Name { get; } string Type { get; } - Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken); - Task AddMetadata(ListingsProviderInfo info, List channels, CancellationToken cancellationToken); + Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, 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/TunerChannelMapping.cs b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs index c1d1c413e8..3b2df0471e 100644 --- a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs +++ b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs @@ -3,8 +3,8 @@ public class TunerChannelMapping { public string Name { get; set; } - public string Number { get; set; } - public string ProviderChannelNumber { get; set; } public string ProviderChannelName { get; set; } + public string ProviderChannelId { get; set; } + public string Id { get; set; } } } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 428651ed5b..f4d45c7e0c 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -47,12 +47,11 @@ namespace MediaBrowser.Controller.Providers /// /// The item. /// The URL. - /// The resource pool. /// The type. /// Index of the image. /// The cancellation token. /// Task. - Task SaveImage(IHasImages item, string url, SemaphoreSlim resourcePool, ImageType type, int? imageIndex, CancellationToken cancellationToken); + Task SaveImage(IHasImages item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken); /// /// Saves the image. diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 3c2ace8964..5cf52e0efe 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -96,17 +96,5 @@ 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.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 5146df6e6d..a65453a788 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -16,7 +16,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.Manager @@ -234,6 +233,7 @@ namespace MediaBrowser.Providers.Manager return retryPath; } + private SemaphoreSlim _imageSaveSemaphore = new SemaphoreSlim(1, 1); /// /// Saves the image to location. /// @@ -247,11 +247,13 @@ namespace MediaBrowser.Providers.Manager var parentFolder = Path.GetDirectoryName(path); - _libraryMonitor.ReportFileSystemChangeBeginning(path); - _libraryMonitor.ReportFileSystemChangeBeginning(parentFolder); + await _imageSaveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { + _libraryMonitor.ReportFileSystemChangeBeginning(path); + _libraryMonitor.ReportFileSystemChangeBeginning(parentFolder); + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); // If the file is currently hidden we'll have to remove that or the save will fail @@ -283,6 +285,8 @@ namespace MediaBrowser.Providers.Manager } finally { + _imageSaveSemaphore.Release(); + _libraryMonitor.ReportFileSystemChangeComplete(path, false); _libraryMonitor.ReportFileSystemChangeComplete(parentFolder, false); } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 9c6d6a482d..bdfe13c1d2 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -253,7 +253,7 @@ namespace MediaBrowser.Providers.Manager { try { - await ProviderManager.SaveImage(personEntity, imageUrl, null, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); + await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); return; } catch (Exception ex) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 003e7b9fad..0b8dca2eb0 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -123,12 +123,11 @@ namespace MediaBrowser.Providers.Manager return Task.FromResult(ItemUpdateType.None); } - public async Task SaveImage(IHasImages item, string url, SemaphoreSlim resourcePool, ImageType type, int? imageIndex, CancellationToken cancellationToken) + public async Task SaveImage(IHasImages item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken) { var response = await _httpClient.GetResponse(new HttpRequestOptions { CancellationToken = cancellationToken, - ResourcePool = resourcePool, Url = url, BufferContent = false diff --git a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs index db551b7638..73668b73c6 100644 --- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs @@ -127,14 +127,7 @@ namespace MediaBrowser.Providers.Omdb } } - using (var stream = await _httpClient.Get(new HttpRequestOptions - { - Url = url, - ResourcePool = OmdbProvider.ResourcePool, - CancellationToken = cancellationToken, - BufferContent = true - - }).ConfigureAwait(false)) + using (var stream = await OmdbProvider.GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) { var resultList = new List(); diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs index f5086ce98e..07b35c45ae 100644 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs @@ -296,14 +296,7 @@ namespace MediaBrowser.Providers.Omdb var url = string.Format("https://www.omdbapi.com/?i={0}&plot=full&tomatoes=true&r=json", imdbParam); - using (var stream = await _httpClient.Get(new HttpRequestOptions - { - Url = url, - ResourcePool = ResourcePool, - CancellationToken = cancellationToken, - BufferContent = true - - }).ConfigureAwait(false)) + using (var stream = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) { var rootObject = _jsonSerializer.DeserializeFromStream(stream); _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); @@ -337,14 +330,7 @@ namespace MediaBrowser.Providers.Omdb var url = string.Format("https://www.omdbapi.com/?i={0}&season={1}&detail=full", imdbParam, seasonId); - using (var stream = await _httpClient.Get(new HttpRequestOptions - { - Url = url, - ResourcePool = ResourcePool, - CancellationToken = cancellationToken, - BufferContent = true - - }).ConfigureAwait(false)) + using (var stream = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) { var rootObject = _jsonSerializer.DeserializeFromStream(stream); _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); @@ -354,6 +340,17 @@ namespace MediaBrowser.Providers.Omdb return path; } + public static Task GetOmdbResponse(IHttpClient httpClient, string url, CancellationToken cancellationToken) + { + return httpClient.Get(new HttpRequestOptions + { + Url = url, + ResourcePool = ResourcePool, + CancellationToken = cancellationToken, + BufferContent = true + }); + } + internal string GetDataFilePath(string imdbId) { if (string.IsNullOrEmpty(imdbId))