فهرست منبع

Merge pull request #2440 from MediaBrowser/dev

Dev
Luke 8 سال پیش
والد
کامیت
77b0cabfee

+ 1 - 1
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)
                             {

+ 1 - 2
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<ItemImageInfo> 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);
 

+ 129 - 32
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<ChannelInfo> 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<string, List<ChannelInfo>> _epgChannels =
+            new ConcurrentDictionary<string, List<ChannelInfo>>(StringComparer.OrdinalIgnoreCase);
+
+        private async Task<List<ChannelInfo>> GetEpgChannels(IListingsProvider provider, ListingsProviderInfo info, bool enableCache, CancellationToken cancellationToken)
+        {
+            List<ChannelInfo> 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<ChannelInfo> 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<NameValuePair> 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<ChannelInfo> epgChannels)
+        {
+            return GetEpgChannelFromTunerChannel(info.ChannelMappings.ToList(), tunerChannel, epgChannels);
+        }
+
+        public ChannelInfo GetEpgChannelFromTunerChannel(List<NameValuePair> mappings, ChannelInfo tunerChannel, List<ChannelInfo> 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<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken)
         {
             var list = new List<ChannelInfo>();
@@ -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<ProgramInfo> 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<ProgramInfo>();
+                }
+                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<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

+ 83 - 201
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<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
+        public async Task<IEnumerable<ProgramInfo>> 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<ProgramInfo> programsInfo = new List<ProgramInfo>();
 
             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<ScheduleDirect.RequestScheduleForChannel> 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<List<ScheduleDirect.Day>>(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<string, ScheduleDirect.Station> 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<string, ScheduleDirect.Station> cache;
-                if (_channelPairingCache.TryGetValue(listingsId, out cache))
-                {
-                    cache[channelNumber] = schChannel;
-                }
-                else
-                {
-                    cache = new Dictionary<string, ScheduleDirect.Station>();
-                    cache[channelNumber] = schChannel;
-                    _channelPairingCache[listingsId] = cache;
-                }
-            }
-        }
-
-        private void ClearPairCache(string listingsId)
-        {
-            lock (_channelCacheLock)
+            if (string.IsNullOrWhiteSpace(channelNumber))
             {
-                Dictionary<string, ScheduleDirect.Station> cache;
-                if (_channelPairingCache.TryGetValue(listingsId, out cache))
-                {
-                    cache.Clear();
-                }
+                channelNumber = map.channel;
             }
-        }
-
-        private int GetChannelPairCacheCount(string listingsId)
-        {
-            lock (_channelCacheLock)
+            if (string.IsNullOrWhiteSpace(channelNumber))
             {
-                Dictionary<string, ScheduleDirect.Station> 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<ChannelInfo> 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<ScheduleDirect.Channel>(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<ScheduleDirect.Station>()).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<ChannelInfo>(), 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<ScheduleDirect.Channel>(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<ScheduleDirect.Station>();
+
                 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<ScheduleDirect.Station> 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

+ 7 - 24
Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs

@@ -106,8 +106,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             return cacheFile;
         }
 
-        public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
+        public async Task<IEnumerable<ProgramInfo>> 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<ChannelInfo> 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

+ 17 - 15
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -2884,20 +2884,20 @@ namespace Emby.Server.Implementations.LiveTv
             _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
         }
 
-        public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
+        public async Task<TunerChannelMapping> 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<RefreshChannelsScheduledTask>();
 
-            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<NameValuePair> mappings, List<ChannelInfo> providerChannels)
+        public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, List<NameValuePair> mappings, List<ChannelInfo> 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;

+ 39 - 25
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<string, string> attributes)
         {
             var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
@@ -295,6 +296,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             return name;
         }
 
+        private string GetTunerChannelId(Dictionary<string, string> attributes)
+        {
+            string result;
+            attributes.TryGetValue("tvg-id", out result);
+
+            if (string.IsNullOrWhiteSpace(result))
+            {
+                attributes.TryGetValue("channel-id", out result);
+            }
+
+            return result;
+        }
+
         private Dictionary<string, string> ParseExtInf(string line, out string remaining)
         {
             var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

+ 1 - 1
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))
             {

+ 1 - 1
MediaBrowser.Api/Images/RemoteImageService.cs

@@ -210,7 +210,7 @@ namespace MediaBrowser.Api.Images
         /// <returns>Task.</returns>
         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);
         }

+ 4 - 4
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<object> 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<object> 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(),
 

+ 2 - 4
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();

+ 2 - 0
MediaBrowser.Controller/LiveTv/ChannelInfo.cs

@@ -27,6 +27,8 @@ namespace MediaBrowser.Controller.LiveTv
 
         public string TunerChannelId { get; set; }
 
+        public string CallSign { get; set; }
+
         /// <summary>
         /// Gets or sets the tuner host identifier.
         /// </summary>

+ 1 - 2
MediaBrowser.Controller/LiveTv/IListingsProvider.cs

@@ -11,8 +11,7 @@ namespace MediaBrowser.Controller.LiveTv
     {
         string Name { get; }
         string Type { get; }
-        Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken);
-        Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken);
+        Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken);
         Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings);
         Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location);
         Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken);

+ 2 - 2
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; }
     }
 }

+ 1 - 2
MediaBrowser.Controller/Providers/IProviderManager.cs

@@ -47,12 +47,11 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="url">The URL.</param>
-        /// <param name="resourcePool">The resource pool.</param>
         /// <param name="type">The type.</param>
         /// <param name="imageIndex">Index of the image.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        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);
 
         /// <summary>
         /// Saves the image.

+ 0 - 12
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;
-        }
     }
 }

+ 7 - 3
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);
         /// <summary>
         /// Saves the image to location.
         /// </summary>
@@ -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);
             }

+ 1 - 1
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)

+ 1 - 2
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
 

+ 1 - 8
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<SearchResult>();
 

+ 13 - 16
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<RootObject>(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<SeasonRootObject>(stream);
                 _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
@@ -354,6 +340,17 @@ namespace MediaBrowser.Providers.Omdb
             return path;
         }
 
+        public static Task<Stream> 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))