|  | @@ -31,8 +31,6 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      public class ChannelManager : IChannelManager
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  | -        internal IChannel[] Channels { get; private set; }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          private readonly IUserManager _userManager;
 | 
	
		
			
				|  |  |          private readonly IUserDataManager _userDataManager;
 | 
	
		
			
				|  |  |          private readonly IDtoService _dtoService;
 | 
	
	
		
			
				|  | @@ -43,11 +41,16 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |          private readonly IJsonSerializer _jsonSerializer;
 | 
	
		
			
				|  |  |          private readonly IProviderManager _providerManager;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
 | 
	
		
			
				|  |  | +            new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          public ChannelManager(
 | 
	
		
			
				|  |  |              IUserManager userManager,
 | 
	
		
			
				|  |  |              IDtoService dtoService,
 | 
	
		
			
				|  |  |              ILibraryManager libraryManager,
 | 
	
		
			
				|  |  | -            ILoggerFactory loggerFactory,
 | 
	
		
			
				|  |  | +            ILogger<ChannelManager> logger,
 | 
	
		
			
				|  |  |              IServerConfigurationManager config,
 | 
	
		
			
				|  |  |              IFileSystem fileSystem,
 | 
	
		
			
				|  |  |              IUserDataManager userDataManager,
 | 
	
	
		
			
				|  | @@ -57,7 +60,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              _userManager = userManager;
 | 
	
		
			
				|  |  |              _dtoService = dtoService;
 | 
	
		
			
				|  |  |              _libraryManager = libraryManager;
 | 
	
		
			
				|  |  | -            _logger = loggerFactory.CreateLogger(nameof(ChannelManager));
 | 
	
		
			
				|  |  | +            _logger = logger;
 | 
	
		
			
				|  |  |              _config = config;
 | 
	
		
			
				|  |  |              _fileSystem = fileSystem;
 | 
	
		
			
				|  |  |              _userDataManager = userDataManager;
 | 
	
	
		
			
				|  | @@ -65,6 +68,8 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              _providerManager = providerManager;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        internal IChannel[] Channels { get; private set; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          private static TimeSpan CacheLength => TimeSpan.FromHours(3);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public void AddParts(IEnumerable<IChannel> channels)
 | 
	
	
		
			
				|  | @@ -85,8 +90,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              var internalChannel = _libraryManager.GetItemById(item.ChannelId);
 | 
	
		
			
				|  |  |              var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var supportsDelete = channel as ISupportsDelete;
 | 
	
		
			
				|  |  | -            return supportsDelete != null && supportsDelete.CanDelete(item);
 | 
	
		
			
				|  |  | +            return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public bool EnableMediaProbe(BaseItem item)
 | 
	
	
		
			
				|  | @@ -146,15 +150,13 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      try
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  | -                        var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                        return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
 | 
	
		
			
				|  |  | +                        return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
 | 
	
		
			
				|  |  | +                            && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                      catch
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          return false;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                  }).ToList();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -171,7 +173,6 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          return false;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                  }).ToList();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -188,9 +189,9 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          return false;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                  }).ToList();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              if (query.IsFavorite.HasValue)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  var val = query.IsFavorite.Value;
 | 
	
	
		
			
				|  | @@ -215,7 +216,6 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          return false;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                  }).ToList();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -226,6 +226,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  all = all.Skip(query.StartIndex.Value).ToList();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              if (query.Limit.HasValue)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  all = all.Take(query.Limit.Value).ToList();
 | 
	
	
		
			
				|  | @@ -256,11 +257,9 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var internalResult = GetChannelsInternal(query);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var dtoOptions = new DtoOptions()
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -            };
 | 
	
		
			
				|  |  | +            var dtoOptions = new DtoOptions();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            //TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
 | 
	
		
			
				|  |  | +            // TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
 | 
	
		
			
				|  |  |              var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var result = new QueryResult<BaseItemDto>
 | 
	
	
		
			
				|  | @@ -341,8 +340,8 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |                  catch
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  return;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -365,11 +364,9 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              var channel = GetChannel(item.ChannelId);
 | 
	
		
			
				|  |  |              var channelPlugin = GetChannelProvider(channel);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |              IEnumerable<MediaSourceInfo> results;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (requiresCallback != null)
 | 
	
		
			
				|  |  | +            if (channelPlugin is IRequiresMediaInfoCallback requiresCallback)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
 | 
	
		
			
				|  |  |                      .ConfigureAwait(false);
 | 
	
	
		
			
				|  | @@ -384,9 +381,6 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  .ToList();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
 | 
	
		
			
				|  |  | -            new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
 | 
	
	
		
			
				|  | @@ -444,18 +438,21 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  isNew = true;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              item.Path = path;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (!item.ChannelId.Equals(id))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  forceUpdate = true;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              item.ChannelId = id;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (item.ParentId != parentFolderId)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  forceUpdate = true;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              item.ParentId = parentFolderId;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
 | 
	
	
		
			
				|  | @@ -472,10 +469,12 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  _libraryManager.CreateItem(item, null);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                ForceSave = !isNew && forceUpdate
 | 
	
		
			
				|  |  | -            }, cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  | +            await item.RefreshMetadata(
 | 
	
		
			
				|  |  | +                new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    ForceSave = !isNew && forceUpdate
 | 
	
		
			
				|  |  | +                },
 | 
	
		
			
				|  |  | +                cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return item;
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -509,12 +508,12 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public ChannelFeatures[] GetAllChannelFeatures()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            return _libraryManager.GetItemIds(new InternalItemsQuery
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                IncludeItemTypes = new[] { typeof(Channel).Name },
 | 
	
		
			
				|  |  | -                OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
 | 
	
		
			
				|  |  | +            return _libraryManager.GetItemIds(
 | 
	
		
			
				|  |  | +                new InternalItemsQuery
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    IncludeItemTypes = new[] { typeof(Channel).Name },
 | 
	
		
			
				|  |  | +                    OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
 | 
	
		
			
				|  |  | +                }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public ChannelFeatures GetChannelFeatures(string id)
 | 
	
	
		
			
				|  | @@ -532,13 +531,13 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public bool SupportsExternalTransfer(Guid channelId)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            //var channel = GetChannel(channelId);
 | 
	
		
			
				|  |  |              var channelProvider = GetChannelProvider(channelId);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return channelProvider.GetChannelFeatures().SupportsContentDownloading;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public ChannelFeatures GetChannelFeaturesDto(Channel channel,
 | 
	
		
			
				|  |  | +        public ChannelFeatures GetChannelFeaturesDto(
 | 
	
		
			
				|  |  | +            Channel channel,
 | 
	
		
			
				|  |  |              IChannel provider,
 | 
	
		
			
				|  |  |              InternalChannelFeatures features)
 | 
	
		
			
				|  |  |          {
 | 
	
	
		
			
				|  | @@ -567,6 +566,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  throw new ArgumentNullException(nameof(name));
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -614,7 +614,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              query.IsFolder = false;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // hack for trailers, figure out a better way later
 | 
	
		
			
				|  |  | -            var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
 | 
	
		
			
				|  |  | +            var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (sortByPremiereDate)
 | 
	
		
			
				|  |  |              {
 | 
	
	
		
			
				|  | @@ -640,10 +640,12 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var query = new InternalItemsQuery();
 | 
	
		
			
				|  |  | -            query.Parent = internalChannel;
 | 
	
		
			
				|  |  | -            query.EnableTotalRecordCount = false;
 | 
	
		
			
				|  |  | -            query.ChannelIds = new Guid[] { internalChannel.Id };
 | 
	
		
			
				|  |  | +            var query = new InternalItemsQuery
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                Parent = internalChannel,
 | 
	
		
			
				|  |  | +                EnableTotalRecordCount = false,
 | 
	
		
			
				|  |  | +                ChannelIds = new Guid[] { internalChannel.Id }
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -651,13 +653,15 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  if (item is Folder folder)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    await GetChannelItemsInternal(new InternalItemsQuery
 | 
	
		
			
				|  |  | -                    {
 | 
	
		
			
				|  |  | -                        Parent = folder,
 | 
	
		
			
				|  |  | -                        EnableTotalRecordCount = false,
 | 
	
		
			
				|  |  | -                        ChannelIds = new Guid[] { internalChannel.Id }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                    await GetChannelItemsInternal(
 | 
	
		
			
				|  |  | +                        new InternalItemsQuery
 | 
	
		
			
				|  |  | +                        {
 | 
	
		
			
				|  |  | +                            Parent = folder,
 | 
	
		
			
				|  |  | +                            EnableTotalRecordCount = false,
 | 
	
		
			
				|  |  | +                            ChannelIds = new Guid[] { internalChannel.Id }
 | 
	
		
			
				|  |  | +                        },
 | 
	
		
			
				|  |  | +                        new SimpleProgress<double>(),
 | 
	
		
			
				|  |  | +                        cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -672,7 +676,8 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var itemsResult = await GetChannelItems(channelProvider,
 | 
	
		
			
				|  |  | +            var itemsResult = await GetChannelItems(
 | 
	
		
			
				|  |  | +                channelProvider,
 | 
	
		
			
				|  |  |                  query.User,
 | 
	
		
			
				|  |  |                  parentItem is Channel ? null : parentItem.ExternalId,
 | 
	
		
			
				|  |  |                  null,
 | 
	
	
		
			
				|  | @@ -684,13 +689,12 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  query.Parent = channel;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              query.ChannelIds = Array.Empty<Guid>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // Not yet sure why this is causing a problem
 | 
	
		
			
				|  |  |              query.GroupByPresentationUniqueKey = false;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            //_logger.LogDebug("GetChannelItemsInternal");
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |              // null if came from cache
 | 
	
		
			
				|  |  |              if (itemsResult != null)
 | 
	
		
			
				|  |  |              {
 | 
	
	
		
			
				|  | @@ -707,12 +711,15 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                      var deadItem = _libraryManager.GetItemById(deadId);
 | 
	
		
			
				|  |  |                      if (deadItem != null)
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  | -                        _libraryManager.DeleteItem(deadItem, new DeleteOptions
 | 
	
		
			
				|  |  | -                        {
 | 
	
		
			
				|  |  | -                            DeleteFileLocation = false,
 | 
	
		
			
				|  |  | -                            DeleteFromExternalProvider = false
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                        }, parentItem, false);
 | 
	
		
			
				|  |  | +                        _libraryManager.DeleteItem(
 | 
	
		
			
				|  |  | +                            deadItem,
 | 
	
		
			
				|  |  | +                            new DeleteOptions
 | 
	
		
			
				|  |  | +                            {
 | 
	
		
			
				|  |  | +                                DeleteFileLocation = false,
 | 
	
		
			
				|  |  | +                                DeleteFromExternalProvider = false
 | 
	
		
			
				|  |  | +                            },
 | 
	
		
			
				|  |  | +                            parentItem,
 | 
	
		
			
				|  |  | +                            false);
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -735,7 +742,6 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              return result;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
 | 
	
		
			
				|  |  |          private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
 | 
	
		
			
				|  |  |              User user,
 | 
	
		
			
				|  |  |              string externalFolderId,
 | 
	
	
		
			
				|  | @@ -743,7 +749,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              bool sortDescending,
 | 
	
		
			
				|  |  |              CancellationToken cancellationToken)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
 | 
	
		
			
				|  |  | +            var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var cacheLength = CacheLength;
 | 
	
		
			
				|  |  |              var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
 | 
	
	
		
			
				|  | @@ -761,11 +767,9 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              catch (FileNotFoundException)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              catch (IOException)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
 | 
	
	
		
			
				|  | @@ -785,11 +789,9 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |                  catch (FileNotFoundException)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |                  catch (IOException)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  var query = new InternalChannelItemQuery
 | 
	
	
		
			
				|  | @@ -833,7 +835,8 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private string GetChannelDataCachePath(IChannel channel,
 | 
	
		
			
				|  |  | +        private string GetChannelDataCachePath(
 | 
	
		
			
				|  |  | +            IChannel channel,
 | 
	
		
			
				|  |  |              string userId,
 | 
	
		
			
				|  |  |              string externalFolderId,
 | 
	
		
			
				|  |  |              ChannelItemSortField? sortField,
 | 
	
	
		
			
				|  | @@ -843,8 +846,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var userCacheKey = string.Empty;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var hasCacheKey = channel as IHasCacheKey;
 | 
	
		
			
				|  |  | -            if (hasCacheKey != null)
 | 
	
		
			
				|  |  | +            if (channel is IHasCacheKey hasCacheKey)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -858,6 +860,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  filename += "-sortField-" + sortField.Value;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              if (sortDescending)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  filename += "-sortDescending";
 | 
	
	
		
			
				|  | @@ -865,7 +868,8 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            return Path.Combine(_config.ApplicationPaths.CachePath,
 | 
	
		
			
				|  |  | +            return Path.Combine(
 | 
	
		
			
				|  |  | +                _config.ApplicationPaths.CachePath,
 | 
	
		
			
				|  |  |                  "channels",
 | 
	
		
			
				|  |  |                  channelId,
 | 
	
		
			
				|  |  |                  version,
 | 
	
	
		
			
				|  | @@ -981,7 +985,6 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  item.RunTimeTicks = null;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |              else if (isNew || !enableMediaProbe)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  item.RunTimeTicks = info.RunTimeTicks;
 | 
	
	
		
			
				|  | @@ -1014,26 +1017,24 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var hasArtists = item as IHasArtist;
 | 
	
		
			
				|  |  | -            if (hasArtists != null)
 | 
	
		
			
				|  |  | +            if (item is IHasArtist hasArtists)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  hasArtists.Artists = info.Artists.ToArray();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var hasAlbumArtists = item as IHasAlbumArtist;
 | 
	
		
			
				|  |  | -            if (hasAlbumArtists != null)
 | 
	
		
			
				|  |  | +            if (item is IHasAlbumArtist hasAlbumArtists)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var trailer = item as Trailer;
 | 
	
		
			
				|  |  | -            if (trailer != null)
 | 
	
		
			
				|  |  | +            if (item is Trailer trailer)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      _logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
 | 
	
		
			
				|  |  |                      forceUpdate = true;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  trailer.TrailerTypes = info.TrailerTypes.ToArray();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1057,6 +1058,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  forceUpdate = true;
 | 
	
		
			
				|  |  |                  _logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              item.ChannelId = internalChannelId;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (!item.ParentId.Equals(parentFolderId))
 | 
	
	
		
			
				|  | @@ -1064,16 +1066,17 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  forceUpdate = true;
 | 
	
		
			
				|  |  |                  _logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              item.ParentId = parentFolderId;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var hasSeries = item as IHasSeries;
 | 
	
		
			
				|  |  | -            if (hasSeries != null)
 | 
	
		
			
				|  |  | +            if (item is IHasSeries hasSeries)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      forceUpdate = true;
 | 
	
		
			
				|  |  |                      _logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  hasSeries.SeriesName = info.SeriesName;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1082,24 +1085,23 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  forceUpdate = true;
 | 
	
		
			
				|  |  |                  _logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              item.ExternalId = info.Id;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var channelAudioItem = item as Audio;
 | 
	
		
			
				|  |  | -            if (channelAudioItem != null)
 | 
	
		
			
				|  |  | +            if (item is Audio channelAudioItem)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  channelAudioItem.ExtraType = info.ExtraType;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  var mediaSource = info.MediaSources.FirstOrDefault();
 | 
	
		
			
				|  |  | -                item.Path = mediaSource == null ? null : mediaSource.Path;
 | 
	
		
			
				|  |  | +                item.Path = mediaSource?.Path;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var channelVideoItem = item as Video;
 | 
	
		
			
				|  |  | -            if (channelVideoItem != null)
 | 
	
		
			
				|  |  | +            if (item is Video channelVideoItem)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  channelVideoItem.ExtraType = info.ExtraType;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  var mediaSource = info.MediaSources.FirstOrDefault();
 | 
	
		
			
				|  |  | -                item.Path = mediaSource == null ? null : mediaSource.Path;
 | 
	
		
			
				|  |  | +                item.Path = mediaSource?.Path;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
 | 
	
	
		
			
				|  | @@ -1156,7 +1158,7 @@ namespace Emby.Server.Implementations.Channels
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
 | 
	
		
			
				|  |  | +            if (isNew || forceUpdate || item.DateLastRefreshed == default)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
 | 
	
		
			
				|  |  |              }
 |