|  | @@ -58,22 +58,23 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          private ILibraryPostScanTask[] PostscanTasks { get; set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  | -        /// Gets the intro providers.
 | 
	
		
			
				|  |  | +        /// Gets or sets the intro providers.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <value>The intro providers.</value>
 | 
	
		
			
				|  |  |          private IIntroProvider[] IntroProviders { get; set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  | -        /// Gets the list of entity resolution ignore rules
 | 
	
		
			
				|  |  | +        /// Gets or sets the list of entity resolution ignore rules
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <value>The entity resolution ignore rules.</value>
 | 
	
		
			
				|  |  |          private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  | -        /// Gets the list of currently registered entity resolvers
 | 
	
		
			
				|  |  | +        /// Gets or sets the list of currently registered entity resolvers
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <value>The entity resolvers enumerable.</value>
 | 
	
		
			
				|  |  |          private IItemResolver[] EntityResolvers { get; set; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          private IMultiItemResolver[] MultiItemResolvers { get; set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
	
		
			
				|  | @@ -83,7 +84,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          private IBaseItemComparer[] Comparers { get; set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  | -        /// Gets the active item repository
 | 
	
		
			
				|  |  | +        /// Gets or sets the active item repository
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <value>The item repository.</value>
 | 
	
		
			
				|  |  |          public IItemRepository ItemRepository { get; set; }
 | 
	
	
		
			
				|  | @@ -133,12 +134,14 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          private readonly Func<IProviderManager> _providerManagerFactory;
 | 
	
		
			
				|  |  |          private readonly Func<IUserViewManager> _userviewManager;
 | 
	
		
			
				|  |  |          public bool IsScanRunning { get; private set; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          private IServerApplicationHost _appHost;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// The _library items cache
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets the library items cache.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
	
		
			
				|  | @@ -150,7 +153,8 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Initializes a new instance of the <see cref="LibraryManager" /> class.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  | -        /// <param name="logger">The logger.</param>
 | 
	
		
			
				|  |  | +        /// <param name="appHost">The application host</param>
 | 
	
		
			
				|  |  | +        /// <param name="loggerFactory">The logger factory.</param>
 | 
	
		
			
				|  |  |          /// <param name="taskManager">The task manager.</param>
 | 
	
		
			
				|  |  |          /// <param name="userManager">The user manager.</param>
 | 
	
		
			
				|  |  |          /// <param name="configurationManager">The configuration manager.</param>
 | 
	
	
		
			
				|  | @@ -167,6 +171,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              Func<IProviderManager> providerManagerFactory,
 | 
	
		
			
				|  |  |              Func<IUserViewManager> userviewManager)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | +            _appHost = appHost;
 | 
	
		
			
				|  |  |              _logger = loggerFactory.CreateLogger(nameof(LibraryManager));
 | 
	
		
			
				|  |  |              _taskManager = taskManager;
 | 
	
		
			
				|  |  |              _userManager = userManager;
 | 
	
	
		
			
				|  | @@ -176,7 +181,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              _fileSystem = fileSystem;
 | 
	
		
			
				|  |  |              _providerManagerFactory = providerManagerFactory;
 | 
	
		
			
				|  |  |              _userviewManager = userviewManager;
 | 
	
		
			
				|  |  | -            _appHost = appHost;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
 | 
	
	
		
			
				|  | @@ -191,8 +196,9 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// <param name="resolvers">The resolvers.</param>
 | 
	
		
			
				|  |  |          /// <param name="introProviders">The intro providers.</param>
 | 
	
		
			
				|  |  |          /// <param name="itemComparers">The item comparers.</param>
 | 
	
		
			
				|  |  | -        /// <param name="postscanTasks">The postscan tasks.</param>
 | 
	
		
			
				|  |  | -        public void AddParts(IEnumerable<IResolverIgnoreRule> rules,
 | 
	
		
			
				|  |  | +        /// <param name="postscanTasks">The post scan tasks.</param>
 | 
	
		
			
				|  |  | +        public void AddParts(
 | 
	
		
			
				|  |  | +            IEnumerable<IResolverIgnoreRule> rules,
 | 
	
		
			
				|  |  |              IEnumerable<IItemResolver> resolvers,
 | 
	
		
			
				|  |  |              IEnumerable<IIntroProvider> introProviders,
 | 
	
		
			
				|  |  |              IEnumerable<IBaseItemComparer> itemComparers,
 | 
	
	
		
			
				|  | @@ -203,24 +209,19 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray();
 | 
	
		
			
				|  |  |              IntroProviders = introProviders.ToArray();
 | 
	
		
			
				|  |  |              Comparers = itemComparers.ToArray();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            PostscanTasks = postscanTasks.OrderBy(i =>
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                var hasOrder = i as IHasOrder;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                return hasOrder == null ? 0 : hasOrder.Order;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            }).ToArray();
 | 
	
		
			
				|  |  | +            PostscanTasks = postscanTasks.ToArray();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// The _root folder
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          private volatile AggregateFolder _rootFolder;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// The _root folder sync lock
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          private readonly object _rootFolderSyncLock = new object();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets the root folder.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
	
		
			
				|  | @@ -239,11 +240,13 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                          }
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  return _rootFolder;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private bool _wizardCompleted;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Records the configuration values.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
	
		
			
				|  | @@ -258,7 +261,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <param name="sender">The sender.</param>
 | 
	
		
			
				|  |  |          /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
 | 
	
		
			
				|  |  | -        void ConfigurationUpdated(object sender, EventArgs e)
 | 
	
		
			
				|  |  | +        private void ConfigurationUpdated(object sender, EventArgs e)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              var config = ConfigurationManager.Configuration;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -278,6 +281,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  throw new ArgumentNullException(nameof(item));
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              if (item is IItemByName)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  if (!(item is MusicArtist))
 | 
	
	
		
			
				|  | @@ -285,18 +289,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      return;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            else if (item.IsFolder)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                //if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel) && !(item is AggregateFolder))
 | 
	
		
			
				|  |  | -                //{
 | 
	
		
			
				|  |  | -                //    if (item.SourceType != SourceType.Library)
 | 
	
		
			
				|  |  | -                //    {
 | 
	
		
			
				|  |  | -                //        return;
 | 
	
		
			
				|  |  | -                //    }
 | 
	
		
			
				|  |  | -                //}
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            else
 | 
	
		
			
				|  |  | +            else if (!item.IsFolder)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  if (!(item is Video) && !(item is LiveTvChannel))
 | 
	
		
			
				|  |  |                  {
 | 
	
	
		
			
				|  | @@ -345,12 +338,14 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                          // channel no longer installed
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  options.DeleteFileLocation = false;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (item is LiveTvProgram)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                _logger.LogDebug("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
 | 
	
		
			
				|  |  | +                _logger.LogDebug(
 | 
	
		
			
				|  |  | +                    "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
 | 
	
		
			
				|  |  |                      item.GetType().Name,
 | 
	
		
			
				|  |  |                      item.Name ?? "Unknown name",
 | 
	
		
			
				|  |  |                      item.Path ?? string.Empty,
 | 
	
	
		
			
				|  | @@ -358,7 +353,8 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              else
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                _logger.LogInformation("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
 | 
	
		
			
				|  |  | +                _logger.LogInformation(
 | 
	
		
			
				|  |  | +                    "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
 | 
	
		
			
				|  |  |                      item.GetType().Name,
 | 
	
		
			
				|  |  |                      item.Name ?? "Unknown name",
 | 
	
		
			
				|  |  |                      item.Path ?? string.Empty,
 | 
	
	
		
			
				|  | @@ -371,19 +367,20 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              foreach (var metadataPath in GetMetadataPaths(item, children))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                _logger.LogDebug("Deleting path {0}", metadataPath);
 | 
	
		
			
				|  |  | +                if (!Directory.Exists(metadataPath))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    continue;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                _logger.LogDebug("Deleting path {MetadataPath}", metadataPath);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  try
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      Directory.Delete(metadataPath, true);
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -                catch (IOException)
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |                  catch (Exception ex)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    _logger.LogError(ex, "Error deleting {metadataPath}", metadataPath);
 | 
	
		
			
				|  |  | +                    _logger.LogError(ex, "Error deleting {MetadataPath}", metadataPath);
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -497,12 +494,13 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  throw new ArgumentNullException(nameof(key));
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              if (type == null)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  throw new ArgumentNullException(nameof(type));
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath))
 | 
	
		
			
				|  |  | +            if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  // Try to normalize paths located underneath program-data in an attempt to make them more portable
 | 
	
		
			
				|  |  |                  key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length)
 | 
	
	
		
			
				|  | @@ -520,13 +518,11 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              return key.GetMD5();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public BaseItem ResolvePath(FileSystemMetadata fileInfo,
 | 
	
		
			
				|  |  | -            Folder parent = null)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            return ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +        public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
 | 
	
		
			
				|  |  | +            => ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private BaseItem ResolvePath(FileSystemMetadata fileInfo,
 | 
	
		
			
				|  |  | +        private BaseItem ResolvePath(
 | 
	
		
			
				|  |  | +            FileSystemMetadata fileInfo,
 | 
	
		
			
				|  |  |              IDirectoryService directoryService,
 | 
	
		
			
				|  |  |              IItemResolver[] resolvers,
 | 
	
		
			
				|  |  |              Folder parent = null,
 | 
	
	
		
			
				|  | @@ -581,7 +577,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          _logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                        files = new FileSystemMetadata[] { };
 | 
	
		
			
				|  |  | +                        files = Array.Empty<FileSystemMetadata>();
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                      else
 | 
	
		
			
				|  |  |                      {
 | 
	
	
		
			
				|  | @@ -609,13 +605,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public bool IgnoreFile(FileSystemMetadata file, BaseItem parent)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)))
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                return true;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            return false;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +            => EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public List<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
 | 
	
		
			
				|  |  |          {
 | 
	
	
		
			
				|  | @@ -655,7 +645,8 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
 | 
	
		
			
				|  |  | +        public IEnumerable<BaseItem> ResolvePaths(
 | 
	
		
			
				|  |  | +            IEnumerable<FileSystemMetadata> files,
 | 
	
		
			
				|  |  |              IDirectoryService directoryService,
 | 
	
		
			
				|  |  |              Folder parent,
 | 
	
		
			
				|  |  |              LibraryOptions libraryOptions,
 | 
	
	
		
			
				|  | @@ -681,6 +672,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                          {
 | 
	
		
			
				|  |  |                              ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService);
 | 
	
		
			
				|  |  |                          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                          items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
 | 
	
		
			
				|  |  |                          return items;
 | 
	
		
			
				|  |  |                      }
 | 
	
	
		
			
				|  | @@ -690,7 +682,8 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers, libraryOptions);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private IEnumerable<BaseItem> ResolveFileList(IEnumerable<FileSystemMetadata> fileList,
 | 
	
		
			
				|  |  | +        private IEnumerable<BaseItem> ResolveFileList(
 | 
	
		
			
				|  |  | +            IEnumerable<FileSystemMetadata> fileList,
 | 
	
		
			
				|  |  |              IDirectoryService directoryService,
 | 
	
		
			
				|  |  |              Folder parent,
 | 
	
		
			
				|  |  |              string collectionType,
 | 
	
	
		
			
				|  | @@ -775,6 +768,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private volatile UserRootFolder _userRootFolder;
 | 
	
		
			
				|  |  |          private readonly object _syncLock = new object();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          public Folder GetUserRootFolder()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              if (_userRootFolder == null)
 | 
	
	
		
			
				|  | @@ -819,8 +813,6 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                  throw new ArgumentNullException(nameof(path));
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            //_logger.LogInformation("FindByPath {0}", path);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |              var query = new InternalItemsQuery
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  Path = path,
 | 
	
	
		
			
				|  | @@ -894,7 +886,6 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <param name="value">The value.</param>
 | 
	
		
			
				|  |  |          /// <returns>Task{Year}.</returns>
 | 
	
		
			
				|  |  | -        /// <exception cref="ArgumentOutOfRangeException"></exception>
 | 
	
		
			
				|  |  |          public Year GetYear(int value)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              if (value <= 0)
 | 
	
	
		
			
				|  | @@ -1036,20 +1027,25 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var rootChildren = RootFolder.Children.ToList();
 | 
	
		
			
				|  |  | -            rootChildren = GetUserRootFolder().Children.ToList();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |              await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // Start by just validating the children of the root, but go no further
 | 
	
		
			
				|  |  | -            await RootFolder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false);
 | 
	
		
			
				|  |  | +            await RootFolder.ValidateChildren(
 | 
	
		
			
				|  |  | +                new SimpleProgress<double>(),
 | 
	
		
			
				|  |  | +                cancellationToken,
 | 
	
		
			
				|  |  | +                new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
 | 
	
		
			
				|  |  | +                recursive: false).ConfigureAwait(false);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            await GetUserRootFolder().ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false).ConfigureAwait(false);
 | 
	
		
			
				|  |  | +            await GetUserRootFolder().ValidateChildren(
 | 
	
		
			
				|  |  | +                new SimpleProgress<double>(),
 | 
	
		
			
				|  |  | +                cancellationToken,
 | 
	
		
			
				|  |  | +                new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
 | 
	
		
			
				|  |  | +                recursive: false).ConfigureAwait(false);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // Quickly scan CollectionFolders for changes
 | 
	
		
			
				|  |  | -            foreach (var folder in GetUserRootFolder().Children.OfType<Folder>().ToList())
 | 
	
		
			
				|  |  | +            foreach (var folder in GetUserRootFolder().Children.OfType<Folder>())
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -1213,7 +1209,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          private string GetCollectionType(string path)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false)
 | 
	
		
			
				|  |  | -                .Select(i => Path.GetFileNameWithoutExtension(i))
 | 
	
		
			
				|  |  | +                .Select(Path.GetFileNameWithoutExtension)
 | 
	
		
			
				|  |  |                  .FirstOrDefault(i => !string.IsNullOrEmpty(i));
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1227,7 +1223,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              if (id == Guid.Empty)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                throw new ArgumentException(nameof(id), "Guid can't be empty");
 | 
	
		
			
				|  |  | +                throw new ArgumentException("Guid can't be empty", nameof(id));
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
 | 
	
	
		
			
				|  | @@ -1395,17 +1391,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (parents.All(i =>
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                if (i is ICollectionFolder || i is UserView)
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    return true;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name);
 | 
	
		
			
				|  |  | -                return false;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            }))
 | 
	
		
			
				|  |  | +            if (parents.All(i => i is ICollectionFolder || i is UserView))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  // Optimize by querying against top level views
 | 
	
		
			
				|  |  |                  query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
 | 
	
	
		
			
				|  | @@ -1461,17 +1447,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            if (parents.All(i =>
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                if (i is ICollectionFolder || i is UserView)
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    return true;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name);
 | 
	
		
			
				|  |  | -                return false;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            }))
 | 
	
		
			
				|  |  | +            if (parents.All(i => i is ICollectionFolder || i is UserView))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  // Optimize by querying against top level views
 | 
	
		
			
				|  |  |                  query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
 | 
	
	
		
			
				|  | @@ -1520,11 +1496,9 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, User user)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var view = item as UserView;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            if (view != null)
 | 
	
		
			
				|  |  | +            if (item is UserView view)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                if (string.Equals(view.ViewType, CollectionType.LiveTv))
 | 
	
		
			
				|  |  | +                if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.Ordinal))
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      return new[] { view.Id };
 | 
	
		
			
				|  |  |                  }
 | 
	
	
		
			
				|  | @@ -1537,8 +1511,10 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          return GetTopParentIdsForQuery(displayParent, user);
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                      return Array.Empty<Guid>();
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  if (!view.ParentId.Equals(Guid.Empty))
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      var displayParent = GetItemById(view.ParentId);
 | 
	
	
		
			
				|  | @@ -1546,6 +1522,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          return GetTopParentIdsForQuery(displayParent, user);
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                      return Array.Empty<Guid>();
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1559,11 +1536,11 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                          .Where(i => user.IsFolderGrouped(i.Id))
 | 
	
		
			
				|  |  |                          .SelectMany(i => GetTopParentIdsForQuery(i, user));
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  return Array.Empty<Guid>();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var collectionFolder = item as CollectionFolder;
 | 
	
		
			
				|  |  | -            if (collectionFolder != null)
 | 
	
		
			
				|  |  | +            if (item is CollectionFolder collectionFolder)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  return collectionFolder.PhysicalFolderIds;
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -1573,6 +1550,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  return new[] { topParent.Id };
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              return Array.Empty<Guid>();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1769,19 +1747,16 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (comparer != null)
 | 
	
		
			
				|  |  | +            // If it requires a user, create a new one, and assign the user
 | 
	
		
			
				|  |  | +            if (comparer is IUserBaseItemComparer)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                // If it requires a user, create a new one, and assign the user
 | 
	
		
			
				|  |  | -                if (comparer is IUserBaseItemComparer)
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType());
 | 
	
		
			
				|  |  | +                var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                    userComparer.User = user;
 | 
	
		
			
				|  |  | -                    userComparer.UserManager = _userManager;
 | 
	
		
			
				|  |  | -                    userComparer.UserDataRepository = _userDataRepository;
 | 
	
		
			
				|  |  | +                userComparer.User = user;
 | 
	
		
			
				|  |  | +                userComparer.UserManager = _userManager;
 | 
	
		
			
				|  |  | +                userComparer.UserDataRepository = _userDataRepository;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                    return userComparer;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +                return userComparer;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return comparer;
 | 
	
	
		
			
				|  | @@ -1792,7 +1767,6 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <param name="item">The item.</param>
 | 
	
		
			
				|  |  |          /// <param name="parent">The parent item.</param>
 | 
	
		
			
				|  |  | -        /// <returns>Task.</returns>
 | 
	
		
			
				|  |  |          public void CreateItem(BaseItem item, BaseItem parent)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              CreateItems(new[] { item }, parent, CancellationToken.None);
 | 
	
	
		
			
				|  | @@ -1802,20 +1776,23 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// Creates the items.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <param name="items">The items.</param>
 | 
	
		
			
				|  |  | +        /// <param name="parent">The parent item</param>
 | 
	
		
			
				|  |  |          /// <param name="cancellationToken">The cancellation token.</param>
 | 
	
		
			
				|  |  | -        /// <returns>Task.</returns>
 | 
	
		
			
				|  |  |          public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            ItemRepository.SaveItems(items, cancellationToken);
 | 
	
		
			
				|  |  | +            // Don't iterate multiple times
 | 
	
		
			
				|  |  | +            var itemsList = items.ToList();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            ItemRepository.SaveItems(itemsList, cancellationToken);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            foreach (var item in items)
 | 
	
		
			
				|  |  | +            foreach (var item in itemsList)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  RegisterItem(item);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (ItemAdded != null)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                foreach (var item in items)
 | 
	
		
			
				|  |  | +                foreach (var item in itemsList)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      // With the live tv guide this just creates too much noise
 | 
	
		
			
				|  |  |                      if (item.SourceType != SourceType.Library)
 | 
	
	
		
			
				|  | @@ -1825,11 +1802,13 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                      try
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  | -                        ItemAdded(this, new ItemChangeEventArgs
 | 
	
		
			
				|  |  | -                        {
 | 
	
		
			
				|  |  | -                            Item = item,
 | 
	
		
			
				|  |  | -                            Parent = parent ?? item.GetParent()
 | 
	
		
			
				|  |  | -                        });
 | 
	
		
			
				|  |  | +                        ItemAdded(
 | 
	
		
			
				|  |  | +                            this,
 | 
	
		
			
				|  |  | +                            new ItemChangeEventArgs
 | 
	
		
			
				|  |  | +                            {
 | 
	
		
			
				|  |  | +                                Item = item,
 | 
	
		
			
				|  |  | +                                Parent = parent ?? item.GetParent()
 | 
	
		
			
				|  |  | +                            });
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                      catch (Exception ex)
 | 
	
		
			
				|  |  |                      {
 | 
	
	
		
			
				|  | @@ -1851,7 +1830,10 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            foreach (var item in items)
 | 
	
		
			
				|  |  | +            // Don't iterate multiple times
 | 
	
		
			
				|  |  | +            var itemsList = items.ToList();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            foreach (var item in itemsList)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  if (item.IsFileProtocol)
 | 
	
		
			
				|  |  |                  {
 | 
	
	
		
			
				|  | @@ -1863,14 +1845,11 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                  RegisterItem(item);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            //var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name;
 | 
	
		
			
				|  |  | -            //_logger.LogDebug("Saving {0} to database.", logName);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            ItemRepository.SaveItems(items, cancellationToken);
 | 
	
		
			
				|  |  | +            ItemRepository.SaveItems(itemsList, cancellationToken);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (ItemUpdated != null)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                foreach (var item in items)
 | 
	
		
			
				|  |  | +                foreach (var item in itemsList)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      // With the live tv guide this just creates too much noise
 | 
	
		
			
				|  |  |                      if (item.SourceType != SourceType.Library)
 | 
	
	
		
			
				|  | @@ -1880,12 +1859,14 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                      try
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  | -                        ItemUpdated(this, new ItemChangeEventArgs
 | 
	
		
			
				|  |  | -                        {
 | 
	
		
			
				|  |  | -                            Item = item,
 | 
	
		
			
				|  |  | -                            Parent = parent,
 | 
	
		
			
				|  |  | -                            UpdateReason = updateReason
 | 
	
		
			
				|  |  | -                        });
 | 
	
		
			
				|  |  | +                        ItemUpdated(
 | 
	
		
			
				|  |  | +                            this,
 | 
	
		
			
				|  |  | +                            new ItemChangeEventArgs
 | 
	
		
			
				|  |  | +                            {
 | 
	
		
			
				|  |  | +                                Item = item,
 | 
	
		
			
				|  |  | +                                Parent = parent,
 | 
	
		
			
				|  |  | +                                UpdateReason = updateReason
 | 
	
		
			
				|  |  | +                            });
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                      catch (Exception ex)
 | 
	
		
			
				|  |  |                      {
 | 
	
	
		
			
				|  | @@ -1899,9 +1880,9 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// Updates the item.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <param name="item">The item.</param>
 | 
	
		
			
				|  |  | +        /// <param name="parent">The parent item.</param>
 | 
	
		
			
				|  |  |          /// <param name="updateReason">The update reason.</param>
 | 
	
		
			
				|  |  |          /// <param name="cancellationToken">The cancellation token.</param>
 | 
	
		
			
				|  |  | -        /// <returns>Task.</returns>
 | 
	
		
			
				|  |  |          public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
 | 
	
	
		
			
				|  | @@ -1911,17 +1892,20 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          /// Reports the item removed.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <param name="item">The item.</param>
 | 
	
		
			
				|  |  | +        /// <param name="parent">The parent item.</param>
 | 
	
		
			
				|  |  |          public void ReportItemRemoved(BaseItem item, BaseItem parent)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              if (ItemRemoved != null)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  try
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    ItemRemoved(this, new ItemChangeEventArgs
 | 
	
		
			
				|  |  | -                    {
 | 
	
		
			
				|  |  | -                        Item = item,
 | 
	
		
			
				|  |  | -                        Parent = parent
 | 
	
		
			
				|  |  | -                    });
 | 
	
		
			
				|  |  | +                    ItemRemoved(
 | 
	
		
			
				|  |  | +                        this,
 | 
	
		
			
				|  |  | +                        new ItemChangeEventArgs
 | 
	
		
			
				|  |  | +                        {
 | 
	
		
			
				|  |  | +                            Item = item,
 | 
	
		
			
				|  |  | +                            Parent = parent
 | 
	
		
			
				|  |  | +                        });
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |                  catch (Exception ex)
 | 
	
		
			
				|  |  |                  {
 | 
	
	
		
			
				|  | @@ -2047,8 +2031,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public string GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var collectionFolder = item as ICollectionFolder;
 | 
	
		
			
				|  |  | -            if (collectionFolder != null)
 | 
	
		
			
				|  |  | +            if (item is ICollectionFolder collectionFolder)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  return collectionFolder.CollectionType;
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -2058,13 +2041,11 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private string GetContentTypeOverride(string path, bool inherit)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path)));
 | 
	
		
			
				|  |  | -            if (nameValuePair != null)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                return nameValuePair.Value;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            return null;
 | 
	
		
			
				|  |  | +            var nameValuePair = ConfigurationManager.Configuration.ContentTypes
 | 
	
		
			
				|  |  | +                                    .FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
 | 
	
		
			
				|  |  | +                                                         || (inherit && !string.IsNullOrEmpty(i.Name)
 | 
	
		
			
				|  |  | +                                                                     && _fileSystem.ContainsSubPath(i.Name, path)));
 | 
	
		
			
				|  |  | +            return nameValuePair?.Value;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private string GetTopFolderContentType(BaseItem item)
 | 
	
	
		
			
				|  | @@ -2081,6 +2062,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      break;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  item = parent;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2092,9 +2074,9 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
 | 
	
		
			
				|  |  | -        //private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromMinutes(1);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public UserView GetNamedView(User user,
 | 
	
		
			
				|  |  | +        public UserView GetNamedView(
 | 
	
		
			
				|  |  | +            User user,
 | 
	
		
			
				|  |  |              string name,
 | 
	
		
			
				|  |  |              string viewType,
 | 
	
		
			
				|  |  |              string sortName)
 | 
	
	
		
			
				|  | @@ -2102,13 +2084,15 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              return GetNamedView(user, name, Guid.Empty, viewType, sortName);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public UserView GetNamedView(string name,
 | 
	
		
			
				|  |  | +        public UserView GetNamedView(
 | 
	
		
			
				|  |  | +            string name,
 | 
	
		
			
				|  |  |              string viewType,
 | 
	
		
			
				|  |  |              string sortName)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath,
 | 
	
		
			
				|  |  | -                                    "views",
 | 
	
		
			
				|  |  | -                                    _fileSystem.GetValidFilename(viewType));
 | 
	
		
			
				|  |  | +            var path = Path.Combine(
 | 
	
		
			
				|  |  | +                ConfigurationManager.ApplicationPaths.InternalMetadataPath,
 | 
	
		
			
				|  |  | +                "views",
 | 
	
		
			
				|  |  | +                _fileSystem.GetValidFilename(viewType));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2144,7 +2128,8 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              return item;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public UserView GetNamedView(User user,
 | 
	
		
			
				|  |  | +        public UserView GetNamedView(
 | 
	
		
			
				|  |  | +            User user,
 | 
	
		
			
				|  |  |              string name,
 | 
	
		
			
				|  |  |              Guid parentId,
 | 
	
		
			
				|  |  |              string viewType,
 | 
	
	
		
			
				|  | @@ -2173,10 +2158,10 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      Name = name,
 | 
	
		
			
				|  |  |                      ViewType = viewType,
 | 
	
		
			
				|  |  |                      ForcedSortName = sortName,
 | 
	
		
			
				|  |  | -                    UserId = user.Id
 | 
	
		
			
				|  |  | +                    UserId = user.Id,
 | 
	
		
			
				|  |  | +                    DisplayParentId = parentId
 | 
	
		
			
				|  |  |                  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                item.DisplayParentId = parentId;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  CreateItem(item, null);
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2193,20 +2178,24 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (refresh)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    // Need to force save to increment DateLastSaved
 | 
	
		
			
				|  |  | -                    ForceSave = true
 | 
	
		
			
				|  |  | +                _providerManagerFactory().QueueRefresh(
 | 
	
		
			
				|  |  | +                    item.Id,
 | 
	
		
			
				|  |  | +                    new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        // Need to force save to increment DateLastSaved
 | 
	
		
			
				|  |  | +                        ForceSave = true
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                }, RefreshPriority.Normal);
 | 
	
		
			
				|  |  | +                    },
 | 
	
		
			
				|  |  | +                    RefreshPriority.Normal);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return item;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public UserView GetShadowView(BaseItem parent,
 | 
	
		
			
				|  |  | -        string viewType,
 | 
	
		
			
				|  |  | -        string sortName)
 | 
	
		
			
				|  |  | +        public UserView GetShadowView(
 | 
	
		
			
				|  |  | +            BaseItem parent,
 | 
	
		
			
				|  |  | +            string viewType,
 | 
	
		
			
				|  |  | +            string sortName)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              if (parent == null)
 | 
	
		
			
				|  |  |              {
 | 
	
	
		
			
				|  | @@ -2257,18 +2246,21 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (refresh)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    // Need to force save to increment DateLastSaved
 | 
	
		
			
				|  |  | -                    ForceSave = true
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                }, RefreshPriority.Normal);
 | 
	
		
			
				|  |  | +                _providerManagerFactory().QueueRefresh(
 | 
	
		
			
				|  |  | +                    item.Id,
 | 
	
		
			
				|  |  | +                    new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        // Need to force save to increment DateLastSaved
 | 
	
		
			
				|  |  | +                        ForceSave = true
 | 
	
		
			
				|  |  | +                    },
 | 
	
		
			
				|  |  | +                    RefreshPriority.Normal);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return item;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public UserView GetNamedView(string name,
 | 
	
		
			
				|  |  | +        public UserView GetNamedView(
 | 
	
		
			
				|  |  | +            string name,
 | 
	
		
			
				|  |  |              Guid parentId,
 | 
	
		
			
				|  |  |              string viewType,
 | 
	
		
			
				|  |  |              string sortName,
 | 
	
	
		
			
				|  | @@ -2331,17 +2323,21 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (refresh)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    // Need to force save to increment DateLastSaved
 | 
	
		
			
				|  |  | -                    ForceSave = true
 | 
	
		
			
				|  |  | -                }, RefreshPriority.Normal);
 | 
	
		
			
				|  |  | +                _providerManagerFactory().QueueRefresh(
 | 
	
		
			
				|  |  | +                    item.Id,
 | 
	
		
			
				|  |  | +                    new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        // Need to force save to increment DateLastSaved
 | 
	
		
			
				|  |  | +                        ForceSave = true
 | 
	
		
			
				|  |  | +                    },
 | 
	
		
			
				|  |  | +                    RefreshPriority.Normal);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return item;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public void AddExternalSubtitleStreams(List<MediaStream> streams,
 | 
	
		
			
				|  |  | +        public void AddExternalSubtitleStreams(
 | 
	
		
			
				|  |  | +            List<MediaStream> streams,
 | 
	
		
			
				|  |  |              string videoPath,
 | 
	
		
			
				|  |  |              string[] files)
 | 
	
		
			
				|  |  |          {
 | 
	
	
		
			
				|  | @@ -2445,6 +2441,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          changed = true;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                      episode.IndexNumber = episodeInfo.EpisodeNumber;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2454,6 +2451,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          changed = true;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                      episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2463,6 +2461,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          changed = true;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                      episode.ParentIndexNumber = episodeInfo.SeasonNumber;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -2492,6 +2491,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private NamingOptions _namingOptions;
 | 
	
		
			
				|  |  |          private string[] _videoFileExtensions;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          private NamingOptions GetNamingOptionsInternal()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              if (_namingOptions == null)
 | 
	
	
		
			
				|  | @@ -2688,7 +2688,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |              var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
 | 
	
		
			
				|  |  |              var changed = false;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (!string.Equals(newPath, path))
 | 
	
		
			
				|  |  | +            if (!string.Equals(newPath, path, StringComparison.Ordinal))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  if (to.IndexOf('/') != -1)
 | 
	
		
			
				|  |  |                  {
 | 
	
	
		
			
				|  | @@ -2812,6 +2812,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          continue;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                      throw;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -2916,6 +2917,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private const string ShortcutFileExtension = ".mblink";
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              AddMediaPathInternal(virtualFolderName, pathInfo, true);
 | 
	
	
		
			
				|  | @@ -2932,7 +2934,7 @@ namespace Emby.Server.Implementations.Library
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (string.IsNullOrWhiteSpace(path))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                throw new ArgumentNullException(nameof(path));
 | 
	
		
			
				|  |  | +                throw new ArgumentException(nameof(path));
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (!Directory.Exists(path))
 |