diff options
Diffstat (limited to 'Emby.Server.Implementations/Library')
25 files changed, 459 insertions, 381 deletions
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index e558fbe27..665d70a41 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library if (fileInfo.IsDirectory) { - if (parent != null) + if (parent is not null) { // Ignore extras folders but allow it at the collection level if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename) @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library } else { - if (parent != null) + if (parent is not null) { // Don't resolve these into audio files if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index cef82ebbc..ea45bf0ba 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -113,6 +113,7 @@ namespace Emby.Server.Implementations.Library /// <param name="imageProcessor">The image processor.</param> /// <param name="memoryCache">The memory cache.</param> /// <param name="namingOptions">The naming options.</param> + /// <param name="directoryService">The directory service.</param> public LibraryManager( IServerApplicationHost appHost, ILoggerFactory loggerFactory, @@ -128,7 +129,8 @@ namespace Emby.Server.Implementations.Library IItemRepository itemRepository, IImageProcessor imageProcessor, IMemoryCache memoryCache, - NamingOptions namingOptions) + NamingOptions namingOptions, + IDirectoryService directoryService) { _appHost = appHost; _logger = loggerFactory.CreateLogger<LibraryManager>(); @@ -146,7 +148,7 @@ namespace Emby.Server.Implementations.Library _memoryCache = memoryCache; _namingOptions = namingOptions; - _extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions); + _extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions, directoryService); _configurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -176,7 +178,7 @@ namespace Emby.Server.Implementations.Library { get { - if (_rootFolder == null) + if (_rootFolder is null) { lock (_rootFolderSyncLock) { @@ -356,8 +358,8 @@ namespace Emby.Server.Implementations.Library } var children = item.IsFolder - ? ((Folder)item).GetRecursiveChildren(false).ToList() - : new List<BaseItem>(); + ? ((Folder)item).GetRecursiveChildren(false) + : Enumerable.Empty<BaseItem>(); foreach (var metadataPath in GetMetadataPaths(item, children)) { @@ -465,9 +467,9 @@ namespace Emby.Server.Implementations.Library private BaseItem ResolveItem(ItemResolveArgs args, IItemResolver[] resolvers) { var item = (resolvers ?? EntityResolvers).Select(r => Resolve(args, r)) - .FirstOrDefault(i => i != null); + .FirstOrDefault(i => i is not null); - if (item != null) + if (item is not null) { ResolverHelper.SetInitialItemValues(item, args, _fileSystem, this); } @@ -495,11 +497,7 @@ namespace Emby.Server.Implementations.Library private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive) { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentNullException(nameof(key)); - } - + ArgumentException.ThrowIfNullOrEmpty(key); ArgumentNullException.ThrowIfNull(type); string programDataPath = _configurationManager.ApplicationPaths.ProgramDataPath; @@ -536,12 +534,12 @@ namespace Emby.Server.Implementations.Library var fullPath = fileInfo.FullName; - if (string.IsNullOrEmpty(collectionType) && parent != null) + if (string.IsNullOrEmpty(collectionType) && parent is not null) { collectionType = GetContentTypeOverride(fullPath, true); } - var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService) + var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, this) { Parent = parent, FileInfo = fileInfo, @@ -572,7 +570,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - if (parent != null && parent.IsPhysicalRoot) + if (parent is not null && parent.IsPhysicalRoot) { _logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf); @@ -654,9 +652,9 @@ namespace Emby.Server.Implementations.Library { var fileList = files.Where(i => !IgnoreFile(i, parent)).ToList(); - if (parent != null) + if (parent is not null) { - var multiItemResolvers = resolvers == null ? MultiItemResolvers : resolvers.OfType<IMultiItemResolver>().ToArray(); + var multiItemResolvers = resolvers is null ? MultiItemResolvers : resolvers.OfType<IMultiItemResolver>().ToArray(); foreach (var resolver in multiItemResolvers) { @@ -697,7 +695,7 @@ namespace Emby.Server.Implementations.Library _logger.LogError(ex, "Error resolving path {Path}", file.FullName); } - if (result != null) + if (result is not null) { yield return result; } @@ -750,7 +748,7 @@ namespace Emby.Server.Implementations.Library var dbItem = GetItemById(folder.Id) as BasePluginFolder; - if (dbItem != null && string.Equals(dbItem.Path, folder.Path, StringComparison.OrdinalIgnoreCase)) + if (dbItem is not null && string.Equals(dbItem.Path, folder.Path, StringComparison.OrdinalIgnoreCase)) { folder = dbItem; } @@ -770,11 +768,11 @@ namespace Emby.Server.Implementations.Library public Folder GetUserRootFolder() { - if (_userRootFolder == null) + if (_userRootFolder is null) { lock (_userRootFolderSyncLock) { - if (_userRootFolder == null) + if (_userRootFolder is null) { var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; @@ -792,7 +790,7 @@ namespace Emby.Server.Implementations.Library _logger.LogError(ex, "Error creating UserRootFolder {Path}", newItemId); } - if (tmpItem == null) + if (tmpItem is null) { _logger.LogDebug("Creating new userRootFolder with DeepCopy"); tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>(); @@ -818,10 +816,7 @@ namespace Emby.Server.Implementations.Library { // If this returns multiple items it could be tricky figuring out which one is correct. // In most cases, the newest one will be and the others obsolete but not yet cleaned up - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } + ArgumentException.ThrowIfNullOrEmpty(path); var query = new InternalItemsQuery { @@ -952,7 +947,7 @@ namespace Emby.Server.Implementations.Library .Cast<T>() .FirstOrDefault(); - if (existing != null) + if (existing is not null) { return existing; } @@ -961,7 +956,7 @@ namespace Emby.Server.Implementations.Library var path = getPathFn(name); var id = GetItemByNameId<T>(path); var item = GetItemById(id) as T; - if (item == null) + if (item is null) { item = new T { @@ -1161,7 +1156,7 @@ namespace Emby.Server.Implementations.Library .ToList(); } - private VirtualFolderInfo GetVirtualFolderInfo(string dir, List<BaseItem> allCollectionFolders, Dictionary<Guid, Guid> refreshQueue) + private VirtualFolderInfo GetVirtualFolderInfo(string dir, List<BaseItem> allCollectionFolders, HashSet<Guid> refreshQueue) { var info = new VirtualFolderInfo { @@ -1181,30 +1176,30 @@ namespace Emby.Server.Implementations.Library return null; } }) - .Where(i => i != null) - .OrderBy(i => i) + .Where(i => i is not null) + .Order() .ToArray(), CollectionType = GetCollectionType(dir) }; var libraryFolder = allCollectionFolders.FirstOrDefault(i => string.Equals(i.Path, dir, StringComparison.OrdinalIgnoreCase)); - - if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary)) + if (libraryFolder is not null) { - info.PrimaryImageItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture); - } + var libraryFolderId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture); + info.ItemId = libraryFolderId; + if (libraryFolder.HasImage(ImageType.Primary)) + { + info.PrimaryImageItemId = libraryFolderId; + } - if (libraryFolder != null) - { - info.ItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture); info.LibraryOptions = GetLibraryOptions(libraryFolder); - if (refreshQueue != null) + if (refreshQueue is not null) { info.RefreshProgress = libraryFolder.GetRefreshProgress(); - info.RefreshStatus = info.RefreshProgress.HasValue ? "Active" : refreshQueue.ContainsKey(libraryFolder.Id) ? "Queued" : "Idle"; + info.RefreshStatus = info.RefreshProgress.HasValue ? "Active" : refreshQueue.Contains(libraryFolder.Id) ? "Queued" : "Idle"; } } @@ -1245,7 +1240,7 @@ namespace Emby.Server.Implementations.Library item = RetrieveItem(id); - if (item != null) + if (item is not null) { RegisterItem(item); } @@ -1258,18 +1253,25 @@ namespace Emby.Server.Implementations.Library if (query.Recursive && !query.ParentId.Equals(default)) { var parent = GetItemById(query.ParentId); - if (parent != null) + if (parent is not null) { - SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); + SetTopParentIdsOrAncestors(query, new[] { parent }); } } - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User, allowExternalContent); } - return _itemRepository.GetItemList(query); + var itemList = _itemRepository.GetItemList(query); + var user = query.User; + if (user is not null) + { + return itemList.Where(i => i.IsVisible(user)).ToList(); + } + + return itemList; } public List<BaseItem> GetItemList(InternalItemsQuery query) @@ -1282,13 +1284,13 @@ namespace Emby.Server.Implementations.Library if (query.Recursive && !query.ParentId.Equals(default)) { var parent = GetItemById(query.ParentId); - if (parent != null) + if (parent is not null) { - SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); + SetTopParentIdsOrAncestors(query, new[] { parent }); } } - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1302,7 +1304,7 @@ namespace Emby.Server.Implementations.Library if (query.AncestorIds.Length == 0 && query.TopParentIds.Length == 0) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1313,7 +1315,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<BaseItem> QueryItems(InternalItemsQuery query) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1331,7 +1333,7 @@ namespace Emby.Server.Implementations.Library public List<Guid> GetItemIds(InternalItemsQuery query) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1341,7 +1343,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1352,7 +1354,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1363,7 +1365,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1374,7 +1376,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1385,7 +1387,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1426,7 +1428,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1440,13 +1442,13 @@ namespace Emby.Server.Implementations.Library if (query.Recursive && !query.ParentId.Equals(default)) { var parent = GetItemById(query.ParentId); - if (parent != null) + if (parent is not null) { - SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); + SetTopParentIdsOrAncestors(query, new[] { parent }); } } - if (query.User != null) + if (query.User is not null) { AddUserToQuery(query, query.User); } @@ -1462,7 +1464,7 @@ namespace Emby.Server.Implementations.Library _itemRepository.GetItemList(query)); } - private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents) + private void SetTopParentIdsOrAncestors(InternalItemsQuery query, IReadOnlyCollection<BaseItem> parents) { if (parents.All(i => i is ICollectionFolder || i is UserView)) { @@ -1508,6 +1510,12 @@ namespace Emby.Server.Implementations.Library }); query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).ToArray(); + + // Prevent searching in all libraries due to empty filter + if (query.TopParentIds.Length == 0) + { + query.TopParentIds = new[] { Guid.NewGuid() }; + } } } @@ -1524,7 +1532,7 @@ namespace Emby.Server.Implementations.Library if (!view.DisplayParentId.Equals(default)) { var displayParent = GetItemById(view.DisplayParentId); - if (displayParent != null) + if (displayParent is not null) { return GetTopParentIdsForQuery(displayParent, user); } @@ -1535,7 +1543,7 @@ namespace Emby.Server.Implementations.Library if (!view.ParentId.Equals(default)) { var displayParent = GetItemById(view.ParentId); - if (displayParent != null) + if (displayParent is not null) { return GetTopParentIdsForQuery(displayParent, user); } @@ -1544,7 +1552,7 @@ namespace Emby.Server.Implementations.Library } // Handle grouping - if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) + if (user is not null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) { return GetUserRootFolder() @@ -1564,7 +1572,7 @@ namespace Emby.Server.Implementations.Library } var topParent = item.GetTopParent(); - if (topParent != null) + if (topParent is not null) { return new[] { topParent.Id }; } @@ -1589,7 +1597,7 @@ namespace Emby.Server.Implementations.Library return items .SelectMany(i => i.ToArray()) .Select(ResolveIntro) - .Where(i => i != null); + .Where(i => i is not null); } /// <summary> @@ -1609,7 +1617,7 @@ namespace Emby.Server.Implementations.Library { _logger.LogError(ex, "Error getting intros"); - return new List<IntroInfo>(); + return Enumerable.Empty<IntroInfo>(); } } @@ -1627,7 +1635,7 @@ namespace Emby.Server.Implementations.Library // Get an existing item by Id video = GetItemById(info.ItemId.Value) as Video; - if (video == null) + if (video is null) { _logger.LogError("Unable to locate item with Id {ID}.", info.ItemId.Value); } @@ -1639,7 +1647,7 @@ namespace Emby.Server.Implementations.Library // Try to resolve the path into a video video = ResolvePath(_fileSystem.GetFileSystemInfo(info.Path)) as Video; - if (video == null) + if (video is null) { _logger.LogError("Intro resolver returned null for {Path}.", info.Path); } @@ -1648,7 +1656,7 @@ namespace Emby.Server.Implementations.Library // Pull the saved db item that will include metadata var dbItem = GetItemById(video.Id) as Video; - if (dbItem != null) + if (dbItem is not null) { video = dbItem; } @@ -1685,7 +1693,7 @@ namespace Emby.Server.Implementations.Library IOrderedEnumerable<BaseItem> orderedItems = null; - foreach (var orderBy in sortBy.Select(o => GetComparer(o, user)).Where(c => c != null)) + foreach (var orderBy in sortBy.Select(o => GetComparer(o, user)).Where(c => c is not null)) { if (isFirst) { @@ -1711,7 +1719,7 @@ namespace Emby.Server.Implementations.Library foreach (var (name, sortOrder) in orderBy) { var comparer = GetComparer(name, user); - if (comparer == null) + if (comparer is null) { continue; } @@ -1781,7 +1789,7 @@ namespace Emby.Server.Implementations.Library RegisterItem(item); } - if (ItemAdded != null) + if (ItemAdded is not null) { foreach (var item in items) { @@ -1811,7 +1819,7 @@ namespace Emby.Server.Implementations.Library private bool ImageNeedsRefresh(ItemImageInfo image) { - if (image.Path != null && image.IsLocalFile) + if (image.Path is not null && image.IsLocalFile) { if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash)) { @@ -1829,7 +1837,7 @@ namespace Emby.Server.Implementations.Library } } - return image.Path != null && !image.IsLocalFile; + return image.Path is not null && !image.IsLocalFile; } /// <inheritdoc /> @@ -1838,7 +1846,7 @@ namespace Emby.Server.Implementations.Library ArgumentNullException.ThrowIfNull(item); var outdated = forceUpdate - ? item.ImageInfos.Where(i => i.Path != null).ToArray() + ? item.ImageInfos.Where(i => i.Path is not null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); // Skip image processing if current or live tv source if (outdated.Length == 0 || item.SourceType != SourceType.Library) @@ -1884,7 +1892,7 @@ namespace Emby.Server.Implementations.Library catch (Exception ex) { _logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path); - size = new ImageDimensions(0, 0); + size = default; image.Width = 0; image.Height = 0; } @@ -1923,7 +1931,7 @@ namespace Emby.Server.Implementations.Library _itemRepository.SaveItems(items, cancellationToken); - if (ItemUpdated != null) + if (ItemUpdated is not null) { foreach (var item in items) { @@ -1975,7 +1983,7 @@ namespace Emby.Server.Implementations.Library /// <param name="parent">The parent item.</param> public void ReportItemRemoved(BaseItem item, BaseItem parent) { - if (ItemRemoved != null) + if (ItemRemoved is not null) { try { @@ -2006,41 +2014,38 @@ namespace Emby.Server.Implementations.Library public List<Folder> GetCollectionFolders(BaseItem item) { - while (item != null) + return GetCollectionFolders(item, GetUserRootFolder().Children.OfType<Folder>()); + } + + public List<Folder> GetCollectionFolders(BaseItem item, IEnumerable<Folder> allUserRootChildren) + { + while (item is not null) { var parent = item.GetParent(); - if (parent == null || parent is AggregateFolder) + if (parent is AggregateFolder) { break; } - item = parent; - } - - if (item == null) - { - return new List<Folder>(); - } - - return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType<Folder>()); - } + if (parent is null) + { + var owner = item.GetOwner(); - public List<Folder> GetCollectionFolders(BaseItem item, List<Folder> allUserRootChildren) - { - while (item != null) - { - var parent = item.GetParent(); + if (owner is null) + { + break; + } - if (parent == null || parent is AggregateFolder) + item = owner; + } + else { - break; + item = parent; } - - item = parent; } - if (item == null) + if (item is null) { return new List<Folder>(); } @@ -2064,7 +2069,7 @@ namespace Emby.Server.Implementations.Library .Find(folder => folder is CollectionFolder) as CollectionFolder; } - return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); + return collectionFolder is null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); } public string GetContentType(BaseItem item) @@ -2129,7 +2134,7 @@ namespace Emby.Server.Implementations.Library private string GetTopFolderContentType(BaseItem item) { - if (item == null) + if (item is null) { return null; } @@ -2137,7 +2142,7 @@ namespace Emby.Server.Implementations.Library while (!item.ParentId.Equals(default)) { var parent = item.GetParent(); - if (parent == null || parent is AggregateFolder) + if (parent is null || parent is AggregateFolder) { break; } @@ -2177,7 +2182,7 @@ namespace Emby.Server.Implementations.Library var refresh = false; - if (item == null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase)) + if (item is null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase)) { Directory.CreateDirectory(path); @@ -2225,7 +2230,7 @@ namespace Emby.Server.Implementations.Library var isNew = false; - if (item == null) + if (item is null) { Directory.CreateDirectory(path); @@ -2251,7 +2256,7 @@ namespace Emby.Server.Implementations.Library if (!refresh && !item.DisplayParentId.Equals(default)) { var displayParent = GetItemById(item.DisplayParentId); - refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; + refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; } if (refresh) @@ -2289,7 +2294,7 @@ namespace Emby.Server.Implementations.Library var isNew = false; - if (item == null) + if (item is null) { Directory.CreateDirectory(path); @@ -2315,7 +2320,7 @@ namespace Emby.Server.Implementations.Library if (!refresh && !item.DisplayParentId.Equals(default)) { var displayParent = GetItemById(item.DisplayParentId); - refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; + refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; } if (refresh) @@ -2340,10 +2345,7 @@ namespace Emby.Server.Implementations.Library string sortName, string uniqueId) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(nameof(name)); - } + ArgumentException.ThrowIfNullOrEmpty(name); var parentIdString = parentId.Equals(default) ? null @@ -2362,7 +2364,7 @@ namespace Emby.Server.Implementations.Library var isNew = false; - if (item == null) + if (item is null) { Directory.CreateDirectory(path); @@ -2394,7 +2396,7 @@ namespace Emby.Server.Implementations.Library if (!refresh && !item.DisplayParentId.Equals(default)) { var displayParent = GetItemById(item.DisplayParentId); - refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; + refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; } if (refresh) @@ -2441,7 +2443,7 @@ namespace Emby.Server.Implementations.Library public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) { var series = episode.Series; - bool? isAbsoluteNaming = series != null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); + bool? isAbsoluteNaming = series is not null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); if (!isAbsoluteNaming.Value) { // In other words, no filter applied @@ -2459,10 +2461,10 @@ namespace Emby.Server.Implementations.Library episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming); // Resolve from parent folder if it's not the Season folder var parent = episode.GetParent(); - if (episodeInfo == null && parent.GetType() == typeof(Folder)) + if (episodeInfo is null && parent.GetType() == typeof(Folder)) { episodeInfo = resolver.Resolve(parent.Path, true, null, null, isAbsoluteNaming); - if (episodeInfo != null) + if (episodeInfo is not null) { // add the container episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.'); @@ -2582,7 +2584,7 @@ namespace Emby.Server.Implementations.Library { var season = episode.Season; - if (season != null) + if (season is not null) { episode.ParentIndexNumber = season.IndexNumber; } @@ -2590,9 +2592,9 @@ namespace Emby.Server.Implementations.Library { /* Anime series don't generally have a season in their file name, however, - tvdb needs a season to correctly get the metadata. + TVDb needs a season to correctly get the metadata. Hence, a null season needs to be filled with something. */ - // FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified + // FIXME perhaps this would be better for TVDb parser to ask for season 1 if no season is specified episode.ParentIndexNumber = 1; } @@ -2620,7 +2622,7 @@ namespace Emby.Server.Implementations.Library public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions); - if (ownerVideoInfo == null) + if (ownerVideoInfo is null) { yield break; } @@ -2640,7 +2642,7 @@ namespace Emby.Server.Implementations.Library } var extra = GetExtra(file, extraType.Value); - if (extra != null) + if (extra is not null) { yield return extra; } @@ -2649,7 +2651,7 @@ namespace Emby.Server.Implementations.Library else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType)) { var extra = GetExtra(current, extraType.Value); - if (extra != null) + if (extra is not null) { yield return extra; } @@ -2666,7 +2668,7 @@ namespace Emby.Server.Implementations.Library // Try to retrieve it from the db. If we don't find it, use the resolved version var itemById = GetItemById(extra.Id); - if (itemById != null) + if (itemById is not null) { extra = itemById; } @@ -2681,10 +2683,10 @@ namespace Emby.Server.Implementations.Library public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem) { string newPath; - if (ownerItem != null) + if (ownerItem is not null) { var libraryOptions = GetLibraryOptions(ownerItem); - if (libraryOptions != null) + if (libraryOptions is not null) { foreach (var pathInfo in libraryOptions.PathInfos) { @@ -2753,10 +2755,8 @@ namespace Emby.Server.Implementations.Library return null; } }) - .Where(i => i != null) - .Where(i => query.User == null ? - true : - i.IsVisible(query.User)) + .Where(i => i is not null) + .Where(i => query.User is null || i.IsVisible(query.User)) .ToList(); } @@ -2838,10 +2838,10 @@ namespace Emby.Server.Implementations.Library } var mediaPathInfos = options.PathInfos; - if (mediaPathInfos != null) + if (mediaPathInfos is not null) { var invalidpath = mediaPathInfos.FirstOrDefault(i => !Directory.Exists(i.Path)); - if (invalidpath != null) + if (invalidpath is not null) { throw new ArgumentException("The specified path does not exist: " + invalidpath.Path + "."); } @@ -2853,7 +2853,7 @@ namespace Emby.Server.Implementations.Library { Directory.CreateDirectory(virtualFolderPath); - if (collectionType != null) + if (collectionType is not null) { var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection"); @@ -2862,7 +2862,7 @@ namespace Emby.Server.Implementations.Library CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); - if (mediaPathInfos != null) + if (mediaPathInfos is not null) { foreach (var path in mediaPathInfos) { @@ -2889,7 +2889,7 @@ namespace Emby.Server.Implementations.Library private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken) { - var personsToSave = new List<BaseItem>(); + List<BaseItem> personsToSave = null; foreach (var person in people) { @@ -2931,12 +2931,12 @@ namespace Emby.Server.Implementations.Library if (saveEntity) { - personsToSave.Add(personEntity); + (personsToSave ??= new()).Add(personEntity); await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); } } - if (personsToSave.Count > 0) + if (personsToSave is not null) { CreateItems(personsToSave, null, CancellationToken.None); } @@ -3098,22 +3098,19 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(path)); } - var removeList = new List<NameValuePair>(); + List<NameValuePair> removeList = null; foreach (var contentType in _configurationManager.Configuration.ContentTypes) { - if (string.IsNullOrWhiteSpace(contentType.Name)) - { - removeList.Add(contentType); - } - else if (_fileSystem.AreEqual(path, contentType.Name) + if (string.IsNullOrWhiteSpace(contentType.Name) + || _fileSystem.AreEqual(path, contentType.Name) || _fileSystem.ContainsSubPath(path, contentType.Name)) { - removeList.Add(contentType); + (removeList ??= new()).Add(contentType); } } - if (removeList.Count > 0) + if (removeList is not null) { _configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes .Except(removeList) @@ -3125,10 +3122,7 @@ namespace Emby.Server.Implementations.Library public void RemoveMediaPath(string virtualFolderName, string mediaPath) { - if (string.IsNullOrEmpty(mediaPath)) - { - throw new ArgumentNullException(nameof(mediaPath)); - } + ArgumentException.ThrowIfNullOrEmpty(mediaPath); var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 20624cc7a..936a08da8 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.Library } } - if (mediaInfo == null) + if (mediaInfo is null) { if (addProbeDelay) { @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Library }, cancellationToken).ConfigureAwait(false); - if (cacheFilePath != null) + if (cacheFilePath is not null) { Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); await using FileStream createStream = AsyncFile.OpenWrite(cacheFilePath); @@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.Library var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream == null || audioStream.Index == -1) + if (audioStream is null || audioStream.Index == -1) { mediaSource.DefaultAudioStreamIndex = null; } @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Library } var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - if (videoStream != null) + if (videoStream is not null) { if (!videoStream.BitRate.HasValue) { diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index bfccc7db7..c9a26a30f 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -154,8 +154,8 @@ namespace Emby.Server.Implementations.Library // If file is strm or main media stream is missing, force a metadata refresh with remote probing if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase) - || (item.MediaType == MediaType.Video && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Video)) - || (item.MediaType == MediaType.Audio && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio)))) + || (item.MediaType == MediaType.Video && mediaSources[0].MediaStreams.All(i => i.Type != MediaStreamType.Video)) + || (item.MediaType == MediaType.Audio && mediaSources[0].MediaStreams.All(i => i.Type != MediaStreamType.Audio)))) { await item.RefreshMetadata( new MetadataRefreshOptions(_directoryService) @@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Library source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol); } - if (user != null) + if (user is not null) { SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); @@ -248,7 +248,7 @@ namespace Emby.Server.Implementations.Library if (protocol == MediaProtocol.Http) { - if (path != null) + if (path is not null) { if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase)) { @@ -328,7 +328,7 @@ namespace Emby.Server.Implementations.Library var sources = hasMediaSources.GetMediaSources(enablePathSubstitution); - if (user != null) + if (user is not null) { foreach (var source in sources) { @@ -357,7 +357,7 @@ namespace Emby.Server.Implementations.Library } var culture = _localizationManager.FindLanguageInfo(language); - if (culture != null) + if (culture is not null) { return culture.ThreeLetterISOLanguageNames; } @@ -383,7 +383,7 @@ namespace Emby.Server.Implementations.Library var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; - var audioLangage = defaultAudioIndex == null + var audioLangage = defaultAudioIndex is null ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); @@ -417,13 +417,13 @@ namespace Emby.Server.Implementations.Library public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) { // Item would only be null if the app didn't supply ItemId as part of the live stream open request - var mediaType = item == null ? MediaType.Video : item.MediaType; + var mediaType = item is null ? MediaType.Video : item.MediaType; if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { - var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item); + var userData = item is null ? new UserItemData() : _userDataManager.GetUserData(user, item); - var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections; + var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections; SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); @@ -432,7 +432,7 @@ namespace Emby.Server.Implementations.Library { var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audio != null) + if (audio is not null) { source.DefaultAudioStreamIndex = audio.Index; } @@ -543,7 +543,7 @@ namespace Emby.Server.Implementations.Library var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream == null || audioStream.Index == -1) + if (audioStream is null || audioStream.Index == -1) { mediaSource.DefaultAudioStreamIndex = null; } @@ -553,7 +553,7 @@ namespace Emby.Server.Implementations.Library } var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - if (videoStream != null) + if (videoStream is not null) { if (!videoStream.BitRate.HasValue) { @@ -638,7 +638,7 @@ namespace Emby.Server.Implementations.Library } } - if (mediaInfo == null) + if (mediaInfo is null) { if (addProbeDelay) { @@ -661,7 +661,7 @@ namespace Emby.Server.Implementations.Library }, cancellationToken).ConfigureAwait(false); - if (cacheFilePath != null) + if (cacheFilePath is not null) { Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); await using FileStream createStream = File.Create(cacheFilePath); @@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.Library var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream == null || audioStream.Index == -1) + if (audioStream is null || audioStream.Index == -1) { mediaSource.DefaultAudioStreamIndex = null; } @@ -723,7 +723,7 @@ namespace Emby.Server.Implementations.Library } var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - if (videoStream != null) + if (videoStream is not null) { if (!videoStream.BitRate.HasValue) { @@ -762,10 +762,7 @@ namespace Emby.Server.Implementations.Library public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException(nameof(id)); - } + ArgumentException.ThrowIfNullOrEmpty(id); // TODO probably shouldn't throw here but it is kept for "backwards compatibility" var info = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException(); @@ -774,10 +771,7 @@ namespace Emby.Server.Implementations.Library public ILiveStream GetLiveStreamInfo(string id) { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException(nameof(id)); - } + ArgumentException.ThrowIfNullOrEmpty(id); if (_openStreams.TryGetValue(id, out ILiveStream info)) { @@ -801,10 +795,7 @@ namespace Emby.Server.Implementations.Library public async Task CloseLiveStream(string id) { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException(nameof(id)); - } + ArgumentException.ThrowIfNullOrEmpty(id); await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false); @@ -835,10 +826,7 @@ namespace Emby.Server.Implementations.Library private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key) { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException("Key can't be empty.", nameof(key)); - } + ArgumentException.ThrowIfNullOrEmpty(key); var keys = key.Split(LiveStreamIdDelimeter, 2); diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 609b95772..6aef87c52 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.Library { var defaultStream = sortedStreams.FirstOrDefault(i => i.IsDefault); - if (defaultStream != null) + if (defaultStream is not null) { return defaultStream.Index; } @@ -89,17 +89,7 @@ namespace Emby.Server.Implementations.Library // Give some preference to external text subs for better performance return streams .Where(i => i.Type == type) - .OrderBy(i => - { - var index = languagePreferences.FindIndex(x => string.Equals(x, i.Language, StringComparison.OrdinalIgnoreCase)); - - return index == -1 ? 100 : index; - }) - .ThenBy(i => GetBooleanOrderBy(i.IsDefault)) - .ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream)) - .ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream)) - .ThenBy(i => GetBooleanOrderBy(i.IsExternal)) - .ThenBy(i => i.Index); + .OrderByDescending(i => GetStreamScore(i, languagePreferences)); } public static void SetSubtitleStreamScores( @@ -113,9 +103,9 @@ namespace Emby.Server.Implementations.Library return; } - var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages); + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages).ToList(); - var filteredStreams = new List<MediaStream>(); + List<MediaStream>? filteredStreams = null; if (mode == SubtitlePlaybackMode.Default) { @@ -144,46 +134,26 @@ namespace Emby.Server.Implementations.Library } // load forced subs if we have found no suitable full subtitles - var iterStreams = filteredStreams.Count == 0 + var iterStreams = filteredStreams is null || filteredStreams.Count == 0 ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) : filteredStreams; foreach (var stream in iterStreams) { - stream.Score = GetSubtitleScore(stream, preferredLanguages); + stream.Score = GetStreamScore(stream, preferredLanguages); } } - private static int GetSubtitleScore(MediaStream stream, IReadOnlyList<string> languagePreferences) + internal static int GetStreamScore(MediaStream stream, IReadOnlyList<string> languagePreferences) { - var values = new List<int>(); - var index = languagePreferences.FindIndex(x => string.Equals(x, stream.Language, StringComparison.OrdinalIgnoreCase)); - - values.Add(index == -1 ? 0 : 100 - index); - - values.Add(stream.IsForced ? 1 : 0); - values.Add(stream.IsDefault ? 1 : 0); - values.Add(stream.SupportsExternalStream ? 1 : 0); - values.Add(stream.IsTextSubtitleStream ? 1 : 0); - values.Add(stream.IsExternal ? 1 : 0); - - values.Reverse(); - var scale = 1; - var score = 0; - - foreach (var value in values) - { - score += scale * (value + 1); - scale *= 10; - } - + var score = index == -1 ? 1 : 101 - index; + score = (score * 10) + (stream.IsForced ? 2 : 1); + score = (score * 10) + (stream.IsDefault ? 2 : 1); + score = (score * 10) + (stream.SupportsExternalStream ? 2 : 1); + score = (score * 10) + (stream.IsTextSubtitleStream ? 2 : 1); + score = (score * 10) + (stream.IsExternal ? 2 : 1); return score; } - - private static int GetBooleanOrderBy(bool value) - { - return value ? 0 : 1; - } } } diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 64e7d5446..c4b6b3756 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.IO; using MediaBrowser.Common.Providers; namespace Emby.Server.Implementations.Library @@ -86,24 +87,8 @@ namespace Emby.Server.Implementations.Library return false; } - char oldDirectorySeparatorChar; - char newDirectorySeparatorChar; - // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162 - // The reasoning behind this is that a forward slash likely means it's a Linux path and - // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much). - if (newSubPath.Contains('/', StringComparison.Ordinal)) - { - oldDirectorySeparatorChar = '\\'; - newDirectorySeparatorChar = '/'; - } - else - { - oldDirectorySeparatorChar = '/'; - newDirectorySeparatorChar = '\\'; - } - - path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); - subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); + subPath = subPath.NormalizePath(out var newDirectorySeparatorChar); + path = path.NormalizePath(newDirectorySeparatorChar); // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results // when the sub path matches a similar but in-complete subpath @@ -127,5 +112,82 @@ namespace Emby.Server.Implementations.Library return true; } + + /// <summary> + /// Retrieves the full resolved path and normalizes path separators to the <see cref="Path.DirectorySeparatorChar"/>. + /// </summary> + /// <param name="path">The path to canonicalize.</param> + /// <returns>The fully expanded, normalized path.</returns> + public static string Canonicalize(this string path) + { + return Path.GetFullPath(path).NormalizePath(); + } + + /// <summary> + /// Normalizes the path's directory separator character to the currently defined <see cref="Path.DirectorySeparatorChar"/>. + /// </summary> + /// <param name="path">The path to normalize.</param> + /// <returns>The normalized path string or <see langword="null"/> if the input path is null or empty.</returns> + [return: NotNullIfNotNull(nameof(path))] + public static string? NormalizePath(this string? path) + { + return path.NormalizePath(Path.DirectorySeparatorChar); + } + + /// <summary> + /// Normalizes the path's directory separator character. + /// </summary> + /// <param name="path">The path to normalize.</param> + /// <param name="separator">The separator character the path now uses or <see langword="null"/>.</param> + /// <returns>The normalized path string or <see langword="null"/> if the input path is null or empty.</returns> + [return: NotNullIfNotNull(nameof(path))] + public static string? NormalizePath(this string? path, out char separator) + { + if (string.IsNullOrEmpty(path)) + { + separator = default; + return path; + } + + var newSeparator = '\\'; + + // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162 + // The reasoning behind this is that a forward slash likely means it's a Linux path and + // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much). + if (path.Contains('/', StringComparison.Ordinal)) + { + newSeparator = '/'; + } + + separator = newSeparator; + + return path.NormalizePath(newSeparator); + } + + /// <summary> + /// Normalizes the path's directory separator character to the specified character. + /// </summary> + /// <param name="path">The path to normalize.</param> + /// <param name="newSeparator">The replacement directory separator character. Must be a valid directory separator.</param> + /// <returns>The normalized path.</returns> + /// <exception cref="ArgumentException">Thrown if the new separator character is not a directory separator.</exception> + [return: NotNullIfNotNull(nameof(path))] + public static string? NormalizePath(this string? path, char newSeparator) + { + const char Bs = '\\'; + const char Fs = '/'; + + if (!(newSeparator == Bs || newSeparator == Fs)) + { + throw new ArgumentException("The character must be a directory separator."); + } + + if (string.IsNullOrEmpty(path)) + { + return path; + } + + return newSeparator == Bs ? path.Replace(Fs, newSeparator) : path.Replace(Bs, newSeparator); + } } } diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 4100a74a5..7a61e2607 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -25,13 +25,10 @@ namespace Emby.Server.Implementations.Library public static bool SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService) { // This version of the below method has no ItemResolveArgs, so we have to require the path already being set - if (string.IsNullOrEmpty(item.Path)) - { - throw new ArgumentException("Item must have a Path"); - } + ArgumentException.ThrowIfNullOrEmpty(item.Path); // If the resolver didn't specify this - if (parent != null) + if (parent is not null) { item.SetParent(parent); } @@ -43,7 +40,7 @@ namespace Emby.Server.Implementations.Library // Make sure DateCreated and DateModified have values var fileInfo = directoryService.GetFile(item.Path); - if (fileInfo == null) + if (fileInfo is null) { return false; } @@ -71,7 +68,7 @@ namespace Emby.Server.Implementations.Library } // If the resolver didn't specify this - if (args.Parent != null) + if (args.Parent is not null) { item.SetParent(args.Parent); } @@ -113,7 +110,7 @@ namespace Emby.Server.Implementations.Library { var childData = args.IsDirectory ? args.GetFileSystemEntryByPath(item.Path) : null; - if (childData != null) + if (childData is not null) { SetDateCreated(item, childData); } @@ -140,7 +137,7 @@ namespace Emby.Server.Implementations.Library if (config.UseFileCreationTimeForDateAdded) { // directoryService.getFile may return null - if (info != null) + if (info is not null) { var dateCreated = info.CreationTimeUtc; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 7a6aea9c1..a74f82475 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { var result = ResolveMultipleInternal(parent, files, collectionType); - if (result != null) + if (result is not null) { foreach (var item in result.Items) { @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio // Use regular audio type for mixed libraries, owned items and music if (isMixedCollectionType || - args.Parent == null || + args.Parent is null || isMusicCollectionType) { item = new MediaBrowser.Controller.Entities.Audio.Audio(); @@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio item = new AudioBook(); } - if (item != null) + if (item is not null) { item.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase); @@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio // TODO: Allow GetMultiDiscMovie in here var result = ResolveMultipleAudio(args.Parent, args.GetActualFileSystemChildren(), parseName); - if (result == null || result.Items.Count != 1 || result.Items[0] is not AudioBook item) + if (result is null || result.Items.Count != 1 || result.Items[0] is not AudioBook item) { return null; } @@ -158,7 +158,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName) { var files = new List<FileSystemMetadata>(); - var items = new List<BaseItem>(); var leftOver = new List<FileSystemMetadata>(); // Loop through each child file/folder and see if we find a video @@ -180,10 +179,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var result = new MultiItemResolverResult { ExtraFiles = leftOver, - Items = items + Items = new List<BaseItem>() }; - var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent); + var isInMixedFolder = resolverResult.Count > 1 || (parent is not null && parent.IsTopParent); foreach (var resolvedItem in resolverResult) { @@ -193,7 +192,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio continue; } - if (resolvedItem.Files.Count == 0) + // Until multi-part books are handled letting files stack hides them from browsing in the client + if (resolvedItem.Files.Count == 0 || resolvedItem.Extras.Count > 0 || resolvedItem.AlternateVersions.Count > 0) { continue; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index a922e3685..bbc70701c 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -25,16 +25,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { private readonly ILogger<MusicAlbumResolver> _logger; private readonly NamingOptions _namingOptions; + private readonly IDirectoryService _directoryService; /// <summary> /// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class. /// </summary> /// <param name="logger">The logger.</param> /// <param name="namingOptions">The naming options.</param> - public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, NamingOptions namingOptions) + /// <param name="directoryService">The directory service.</param> + public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) { _logger = logger; _namingOptions = namingOptions; + _directoryService = directoryService; } /// <summary> @@ -109,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio } // If args contains music it's a music album - if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService)) + if (ContainsMusic(args.FileSystemChildren, true, _directoryService)) { return true; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 2538c2b5b..c858dc53d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Emby.Naming.Common; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; @@ -18,19 +19,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio public class MusicArtistResolver : ItemResolver<MusicArtist> { private readonly ILogger<MusicAlbumResolver> _logger; - private NamingOptions _namingOptions; + private readonly NamingOptions _namingOptions; + private readonly IDirectoryService _directoryService; /// <summary> /// Initializes a new instance of the <see cref="MusicArtistResolver"/> class. /// </summary> /// <param name="logger">Instance of the <see cref="MusicAlbumResolver"/> interface.</param> /// <param name="namingOptions">The <see cref="NamingOptions"/>.</param> + /// <param name="directoryService">The directory service.</param> public MusicArtistResolver( ILogger<MusicAlbumResolver> logger, - NamingOptions namingOptions) + NamingOptions namingOptions, + IDirectoryService directoryService) { _logger = logger; _namingOptions = namingOptions; + _directoryService = directoryService; } /// <summary> @@ -78,9 +83,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio return null; } - var directoryService = args.DirectoryService; - - var albumResolver = new MusicAlbumResolver(_logger, _namingOptions); + var albumResolver = new MusicAlbumResolver(_logger, _namingOptions, _directoryService); var directories = args.FileSystemChildren.Where(i => i.IsDirectory); @@ -97,7 +100,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio } // If we contain a music album assume we are an artist folder - if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, directoryService)) + if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, _directoryService)) { // Stop once we see a music album state.Stop(); diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index b2a7abb1b..381796d0e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -25,14 +25,17 @@ namespace Emby.Server.Implementations.Library.Resolvers { private readonly ILogger _logger; - protected BaseVideoResolver(ILogger logger, NamingOptions namingOptions) + protected BaseVideoResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService) { _logger = logger; NamingOptions = namingOptions; + DirectoryService = directoryService; } protected NamingOptions NamingOptions { get; } + protected IDirectoryService DirectoryService { get; } + /// <summary> /// Resolves the specified args. /// </summary> @@ -65,13 +68,26 @@ namespace Emby.Server.Implementations.Library.Resolvers var filename = child.Name; if (child.IsDirectory) { - if (IsDvdDirectory(child.FullName, filename, args.DirectoryService)) + if (IsDvdDirectory(child.FullName, filename, DirectoryService)) { - videoType = VideoType.Dvd; + var videoTmp = new TVideoType + { + Path = args.Path, + VideoType = VideoType.Dvd + }; + Set3DFormat(videoTmp); + return videoTmp; } - else if (IsBluRayDirectory(filename)) + + if (IsBluRayDirectory(filename)) { - videoType = VideoType.BluRay; + var videoTmp = new TVideoType + { + Path = args.Path, + VideoType = VideoType.BluRay + }; + Set3DFormat(videoTmp); + return videoTmp; } } else if (IsDvdFile(filename)) @@ -79,7 +95,7 @@ namespace Emby.Server.Implementations.Library.Resolvers videoType = VideoType.Dvd; } - if (videoType == null) + if (videoType is null) { continue; } @@ -93,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName); } - if (videoInfo == null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions))) + if (videoInfo is null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions))) { return null; } @@ -163,17 +179,15 @@ namespace Emby.Server.Implementations.Library.Resolvers try { // use disc-utils, both DVDs and BDs use UDF filesystem - using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read)) - using (UdfReader udfReader = new UdfReader(videoFileStream)) + using var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read, FileShare.Read); + using UdfReader udfReader = new UdfReader(videoFileStream); + if (udfReader.DirectoryExists("VIDEO_TS")) { - if (udfReader.DirectoryExists("VIDEO_TS")) - { - video.IsoType = IsoType.Dvd; - } - else if (udfReader.DirectoryExists("BDMV")) - { - video.IsoType = IsoType.BluRay; - } + video.IsoType = IsoType.Dvd; + } + else if (udfReader.DirectoryExists("BDMV")) + { + video.IsoType = IsoType.BluRay; } } catch (Exception ex) diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 6fc200e3b..042422c6f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books var extension = Path.GetExtension(args.Path); - if (extension != null && _validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + if (extension is not null && _validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { // It's a book return new Book diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs index 408e640f9..b4791b945 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs @@ -4,6 +4,8 @@ using System.IO; using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; @@ -14,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers /// <summary> /// Resolves a Path into a Video or Video subclass. /// </summary> - internal class ExtraResolver + internal class ExtraResolver : BaseVideoResolver<Video> { private readonly NamingOptions _namingOptions; private readonly IItemResolver[] _trailerResolvers; @@ -25,11 +27,18 @@ namespace Emby.Server.Implementations.Library.Resolvers /// </summary> /// <param name="logger">The logger.</param> /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param> - public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions) + /// <param name="directoryService">The directory service.</param> + public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) + : base(logger, namingOptions, directoryService) { _namingOptions = namingOptions; - _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(logger, namingOptions) }; - _videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(logger, namingOptions) }; + _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(logger, namingOptions, directoryService) }; + _videoResolvers = new IItemResolver[] { this }; + } + + protected override Video Resolve(ItemResolveArgs args) + { + return ResolveVideo<Video>(args, true); } /// <summary> @@ -48,7 +57,7 @@ namespace Emby.Server.Implementations.Library.Resolvers public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType) { var extraResult = GetExtraInfo(path, _namingOptions); - if (extraResult.ExtraType == null) + if (extraResult.ExtraType is null) { extraType = null; return false; diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs index 079962282..1c2de912a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Library.Resolvers { base.SetInitialItemValues(item, args); - item.IsRoot = args.Parent == null; + item.IsRoot = args.Parent is null; } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs index 5e33b402d..ba320266a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs @@ -2,6 +2,7 @@ using Emby.Naming.Common; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library.Resolvers @@ -18,8 +19,9 @@ namespace Emby.Server.Implementations.Library.Resolvers /// </summary> /// <param name="logger">The logger.</param> /// <param name="namingOptions">The naming options.</param> - public GenericVideoResolver(ILogger logger, NamingOptions namingOptions) - : base(logger, namingOptions) + /// <param name="directoryService">The directory service.</param> + public GenericVideoResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService) + : base(logger, namingOptions, directoryService) { } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 8f9e5f01b..ea980b992 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -43,8 +43,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies /// <param name="imageProcessor">The image processor.</param> /// <param name="logger">The logger.</param> /// <param name="namingOptions">The naming options.</param> - public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions) - : base(logger, namingOptions) + /// <param name="directoryService">The directory service.</param> + public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) + : base(logger, namingOptions, directoryService) { _imageProcessor = imageProcessor; } @@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies { var result = ResolveMultipleInternal(parent, files, collectionType); - if (result != null) + if (result is not null) { foreach (var item in result.Items) { @@ -97,18 +98,18 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); + movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - movie = FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); + movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); } if (string.IsNullOrEmpty(collectionType)) { // Owned items will be caught by the video extra resolver - if (args.Parent == null) + if (args.Parent is null) { return null; } @@ -118,19 +119,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true); + movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); } if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) { - movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true); + movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); } // ignore extras - return movie?.ExtraType == null ? movie : null; + return movie?.ExtraType is null ? movie : null; } - if (args.Parent == null) + if (args.Parent is null) { return base.Resolve(args); } @@ -168,12 +169,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } // Ignore extras - if (item?.ExtraType != null) + if (item?.ExtraType is not null) { return null; } - if (item != null) + if (item is not null) { item.IsInMixedFolder = true; } @@ -205,7 +206,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (string.IsNullOrEmpty(collectionType)) { // Owned items should just use the plain video type - if (parent == null) + if (parent is null) { return ResolveVideos<Video>(parent, files, false, collectionType, false); } @@ -268,7 +269,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var videoInfos = files .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName)) - .Where(f => f != null) + .Where(f => f is not null) .ToList(); var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName); @@ -284,7 +285,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies { var firstVideo = video.Files[0]; var path = firstVideo.Path; - if (video.ExtraType != null) + if (video.ExtraType is not null) { result.ExtraFiles.Add(files.Find(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase))); continue; @@ -313,13 +314,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return result; } - private static bool IsIgnored(string filename) - { - // Ignore samples - Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); - - return m.Success; - } + private static bool IsIgnored(ReadOnlySpan<char> filename) + => Regex.IsMatch(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file) { @@ -376,7 +372,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (!justName.IsEmpty) { - // check for tmdb id + // Check for TMDb id var tmdbid = justName.GetAttributeValue("tmdbid"); if (!string.IsNullOrWhiteSpace(tmdbid)) @@ -387,7 +383,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrEmpty(item.Path)) { - // check for imdb id - we use full media path, as we can assume, that this will match in any use case (either id in parent dir or in file name) + // Check for IMDb id - we use full media path, as we can assume that this will match in any use case (whether id in parent dir or in file name) var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid"); if (!string.IsNullOrWhiteSpace(imdbid)) @@ -529,7 +525,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } return false; - }).OrderBy(i => i).ToList(); + }).Order().ToList(); // If different video types were found, don't allow this if (videoTypes.Distinct().Count() > 1) @@ -568,7 +564,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private bool IsInvalid(Folder parent, ReadOnlySpan<char> collectionType) { - if (parent != null) + if (parent is not null) { if (parent.IsRoot) { diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index e11fb262e..9026160ff 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.IO; @@ -12,15 +10,20 @@ using Jellyfin.Extensions; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; namespace Emby.Server.Implementations.Library.Resolvers { + /// <summary> + /// Class PhotoResolver. + /// </summary> public class PhotoResolver : ItemResolver<Photo> { private readonly IImageProcessor _imageProcessor; private readonly NamingOptions _namingOptions; + private readonly IDirectoryService _directoryService; private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @@ -35,10 +38,17 @@ namespace Emby.Server.Implementations.Library.Resolvers "default" }; - public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions) + /// <summary> + /// Initializes a new instance of the <see cref="PhotoResolver"/> class. + /// </summary> + /// <param name="imageProcessor">The image processor.</param> + /// <param name="namingOptions">The naming options.</param> + /// <param name="directoryService">The directory service.</param> + public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions, IDirectoryService directoryService) { _imageProcessor = imageProcessor; _namingOptions = namingOptions; + _directoryService = directoryService; } /// <summary> @@ -61,7 +71,7 @@ namespace Emby.Server.Implementations.Library.Resolvers var filename = Path.GetFileNameWithoutExtension(args.Path); // Make sure the image doesn't belong to a video file - var files = args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)); + var files = _directoryService.GetFiles(Path.GetDirectoryName(args.Path)); foreach (var file in files) { diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index 6b0dfe986..5d569009d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -30,17 +30,20 @@ namespace Emby.Server.Implementations.Library.Resolvers { if (args.IsDirectory) { - // It's a boxset if the path is a directory with [playlist] in it's the name - // TODO: Should this use Path.GetDirectoryName() instead? - bool isBoxSet = Path.GetFileName(args.Path) - ?.Contains("[playlist]", StringComparison.OrdinalIgnoreCase) - ?? false; - if (isBoxSet) + // It's a boxset if the path is a directory with [playlist] in its name + var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path)); + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + if (filename.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)) { return new Playlist { Path = args.Path, - Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim() + Name = filename.Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim(), + OpenAccess = true }; } @@ -51,7 +54,8 @@ namespace Emby.Server.Implementations.Library.Resolvers return new Playlist { Path = args.Path, - Name = Path.GetFileName(args.Path) + Name = filename, + OpenAccess = true }; } } @@ -60,15 +64,16 @@ namespace Emby.Server.Implementations.Library.Resolvers // It should have the correct collection type and a supported file extension else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { - var extension = Path.GetExtension(args.Path); - if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + var extension = Path.GetExtension(args.Path.AsSpan()); + if (Playlist.SupportedExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { return new Playlist { Path = args.Path, Name = Path.GetFileNameWithoutExtension(args.Path), IsInMixedFolder = true, - PlaylistMediaType = MediaType.Audio + PlaylistMediaType = MediaType.Audio, + OpenAccess = true }; } } diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 9ba079edf..392ee4c77 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -5,6 +5,7 @@ using System.Linq; using Emby.Naming.Common; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; @@ -20,8 +21,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// </summary> /// <param name="logger">The logger.</param> /// <param name="namingOptions">The naming options.</param> - public EpisodeResolver(ILogger<EpisodeResolver> logger, NamingOptions namingOptions) - : base(logger, namingOptions) + /// <param name="directoryService">The directory service.</param> + public EpisodeResolver(ILogger<EpisodeResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) + : base(logger, namingOptions, directoryService) { } @@ -34,7 +36,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { var parent = args.Parent; - if (parent == null) + if (parent is null) { return null; } @@ -46,34 +48,34 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // Also handle flat tv folders - if (season != null || + if (season is not null || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || args.HasParent<Series>()) { var episode = ResolveVideo<Episode>(args, false); // Ignore extras - if (episode == null || episode.ExtraType != null) + if (episode is null || episode.ExtraType is not null) { return null; } var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault(); - if (series != null) + if (series is not null) { episode.SeriesId = series.Id; episode.SeriesName = series.Name; } - if (season != null) + if (season is not null) { episode.SeasonId = season.Id; episode.SeasonName = season.Name; } // Assume season 1 if there's no season folder and a season number could not be determined - if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue)) + if (season is null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue)) { episode.ParentIndexNumber = 1; } diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index ea4851458..e9538a5c9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -66,7 +66,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var episodeInfo = resolver.Resolve(testPath, true); - if (episodeInfo?.EpisodeNumber != null && episodeInfo.SeasonNumber.HasValue) + if (episodeInfo?.EpisodeNumber is not null && episodeInfo.SeasonNumber.HasValue) { _logger.LogDebug( "Found folder underneath series with episode number: {0}. Season {1}. Episode {2}", @@ -81,14 +81,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV if (season.IndexNumber.HasValue) { var seasonNumber = season.IndexNumber.Value; - - season.Name = seasonNumber == 0 ? - args.LibraryOptions.SeasonZeroDisplayName : - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("NameSeasonNumber"), - seasonNumber, - args.LibraryOptions.PreferredMetadataLanguage); + if (string.IsNullOrEmpty(season.Name)) + { + var seasonNames = series.SeasonNames; + if (seasonNames.TryGetValue(seasonNumber, out var seasonName)) + { + season.Name = seasonName; + } + else + { + season.Name = seasonNumber == 0 ? + args.LibraryOptions.SeasonZeroDisplayName : + string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("NameSeasonNumber"), + seasonNumber, + args.LibraryOptions.PreferredMetadataLanguage); + } + } } return season; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index f5ac3c665..d4f275bed 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { if (args.ContainsFileSystemEntryByName("tvshow.nfo")) { - if (args.Parent != null && args.Parent.IsRoot) + if (args.Parent is not null && args.Parent.IsRoot) { // For now, return null, but if we want to allow this in the future then add some additional checks to guard against a misplaced tvshow.nfo return null; @@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV }; } - if (args.Parent != null && args.Parent.IsRoot) + if (args.Parent is not null && args.Parent.IsRoot) { return null; } @@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions); var episodeInfo = episodeResolver.Resolve(fullName, false, true, false, fillExtendedInfo: false); - if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue) + if (episodeInfo is not null && episodeInfo.EpisodeNumber.HasValue) { return true; } @@ -184,6 +184,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { var justName = Path.GetFileName(path.AsSpan()); + var imdbId = justName.GetAttributeValue("imdbid"); + if (!string.IsNullOrEmpty(imdbId)) + { + item.SetProviderId(MetadataProvider.Imdb, imdbId); + } + var tvdbId = justName.GetAttributeValue("tvdbid"); if (!string.IsNullOrEmpty(tvdbId)) { diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 60778a443..b916b9170 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -73,10 +73,7 @@ namespace Emby.Server.Implementations.Library { var searchTerm = query.SearchTerm; - if (string.IsNullOrEmpty(searchTerm)) - { - throw new ArgumentException("SearchTerm can't be empty.", nameof(query)); - } + ArgumentException.ThrowIfNullOrEmpty(searchTerm); searchTerm = searchTerm.Trim().RemoveDiacritics(); diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index aecab7d9c..a0a90b129 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library { var userData = _repository.GetUserData(internalUserId, keys); - if (userData != null) + if (userData is not null) { return userData; } diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index ec411aa3b..2c3dc1857 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -46,10 +46,9 @@ namespace Emby.Server.Implementations.Library public Folder[] GetUserViews(UserViewQuery query) { var user = _userManager.GetUserById(query.UserId); - - if (user == null) + if (user is null) { - throw new ArgumentException("User Id specified in the query does not exist.", nameof(query)); + throw new ArgumentException("User id specified in the query does not exist.", nameof(query)); } var folders = _libraryManager.GetUserRootFolder() @@ -58,7 +57,6 @@ namespace Emby.Server.Implementations.Library .ToList(); var groupedFolders = new List<ICollectionFolder>(); - var list = new List<Folder>(); foreach (var folder in folders) @@ -66,13 +64,27 @@ namespace Emby.Server.Implementations.Library var collectionFolder = folder as ICollectionFolder; var folderViewType = collectionFolder?.CollectionType; + // Playlist library requires special handling because the folder only refrences user playlists + if (string.Equals(folderViewType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) + { + var items = folder.GetItemList(new InternalItemsQuery(user) + { + ParentId = folder.ParentId + }); + + if (!items.Any(item => item.IsVisible(user))) + { + continue; + } + } + if (UserView.IsUserSpecific(folder)) { list.Add(_libraryManager.GetNamedView(user, folder.Name, folder.Id, folderViewType, null)); continue; } - if (collectionFolder != null && UserView.IsEligibleForGrouping(folder) && user.IsFolderGrouped(folder.Id)) + if (collectionFolder is not null && UserView.IsEligibleForGrouping(folder) && user.IsFolderGrouped(folder.Id)) { groupedFolders.Add(collectionFolder); continue; @@ -111,10 +123,10 @@ namespace Emby.Server.Implementations.Library if (query.IncludeExternalContent) { - var channelResult = _channelManager.GetChannelsInternal(new ChannelQuery + var channelResult = _channelManager.GetChannelsInternalAsync(new ChannelQuery { UserId = query.UserId - }); + }).GetAwaiter().GetResult(); var channels = channelResult.Items; @@ -132,14 +144,12 @@ namespace Emby.Server.Implementations.Library } var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); - var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews); return list .OrderBy(i => { var index = Array.IndexOf(orders, i.Id); - if (index == -1 && i is UserView view && !view.DisplayParentId.Equals(default)) @@ -208,15 +218,15 @@ namespace Emby.Server.Implementations.Library // Only grab the index container for media var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer; - if (container == null) + if (container is null) { list.Add(new Tuple<BaseItem, List<BaseItem>>(null, new List<BaseItem> { item })); } else { - var current = list.FirstOrDefault(i => i.Item1 != null && i.Item1.Id.Equals(container.Id)); + var current = list.FirstOrDefault(i => i.Item1 is not null && i.Item1.Id.Equals(container.Id)); - if (current != null) + if (current is not null) { current.Item2.Add(item); } @@ -286,7 +296,7 @@ namespace Emby.Server.Implementations.Library if (parents.Count == 0) { - return new List<BaseItem>(); + return Array.Empty<BaseItem>(); } if (includeItemTypes.Length == 0) diff --git a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs index 88b93a211..df45793c3 100644 --- a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs @@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.Library.Validators try { var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet; - if (boxSet == null) + if (boxSet is null) { // won't automatically create collection if only one movie in it if (movieIds.Count >= 2) |
