diff options
Diffstat (limited to 'Emby.Server.Implementations/Library')
19 files changed, 962 insertions, 803 deletions
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index f29a0b3ad7..f9538fbad6 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -38,7 +38,8 @@ namespace Emby.Server.Implementations.Library } // Don't ignore top level folders - if (fileInfo.IsDirectory && parent is AggregateFolder) + if (fileInfo.IsDirectory + && (parent is AggregateFolder || (parent?.IsTopParent ?? false))) { return false; } @@ -48,35 +49,21 @@ namespace Emby.Server.Implementations.Library return true; } - var filename = fileInfo.Name; - - if (fileInfo.IsDirectory) + if (parent is null) { - if (parent is not null) - { - // Ignore extras for unsupported types - if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename) - && parent is not AggregateFolder - && parent is not UserRootFolder) - { - return true; - } - } + return false; } - else + + if (fileInfo.IsDirectory) { - if (parent is not null) - { - // Don't resolve theme songs - if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal) - && AudioFileParser.IsAudioFile(filename, _namingOptions)) - { - return true; - } - } + // Ignore extras for unsupported types + return _namingOptions.AllExtrasTypesFolderNames.ContainsKey(fileInfo.Name) + && parent is not UserRootFolder; } - return false; + // Don't resolve theme songs + return Path.GetFileNameWithoutExtension(fileInfo.Name.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal) + && AudioFileParser.IsAudioFile(fileInfo.Name, _namingOptions); } } } diff --git a/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs b/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs index 2c186c9173..401ca73b80 100644 --- a/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs @@ -20,7 +20,7 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule } var parentDir = directory.Parent; - if (parentDir == null || parentDir.FullName == directory.FullName) + if (parentDir is null) { return null; } @@ -42,6 +42,19 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule /// <returns>True if the file should be ignored.</returns> public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent) { + if (fileInfo.IsDirectory) + { + var dirIgnoreFile = FindIgnoreFile(new DirectoryInfo(fileInfo.FullName)); + if (dirIgnoreFile is null) + { + return false; + } + + // ignore the directory only if the .ignore file is empty + // evaluate individual files otherwise + return string.IsNullOrWhiteSpace(GetFileContent(dirIgnoreFile)); + } + var parentDirPath = Path.GetDirectoryName(fileInfo.FullName); if (string.IsNullOrEmpty(parentDirPath)) { @@ -55,13 +68,9 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule return false; } - string ignoreFileString; - using (var reader = ignoreFile.OpenText()) - { - ignoreFileString = reader.ReadToEnd(); - } + string ignoreFileString = GetFileContent(ignoreFile); - if (string.IsNullOrEmpty(ignoreFileString)) + if (string.IsNullOrWhiteSpace(ignoreFileString)) { // Ignore directory if we just have the file return true; @@ -74,4 +83,12 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule return ignore.IsIgnored(fileInfo.FullName); } + + private static string GetFileContent(FileInfo dirIgnoreFile) + { + using (var reader = dirIgnoreFile.OpenText()) + { + return reader.ReadToEnd(); + } + } } diff --git a/Emby.Server.Implementations/Library/ExternalDataManager.cs b/Emby.Server.Implementations/Library/ExternalDataManager.cs new file mode 100644 index 0000000000..d3cfa1d256 --- /dev/null +++ b/Emby.Server.Implementations/Library/ExternalDataManager.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.MediaSegments; +using MediaBrowser.Controller.Trickplay; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// IExternalDataManager implementation. +/// </summary> +public class ExternalDataManager : IExternalDataManager +{ + private readonly IKeyframeManager _keyframeManager; + private readonly IMediaSegmentManager _mediaSegmentManager; + private readonly IPathManager _pathManager; + private readonly ITrickplayManager _trickplayManager; + private readonly ILogger<ExternalDataManager> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="ExternalDataManager"/> class. + /// </summary> + /// <param name="keyframeManager">The keyframe manager.</param> + /// <param name="mediaSegmentManager">The media segment manager.</param> + /// <param name="pathManager">The path manager.</param> + /// <param name="trickplayManager">The trickplay manager.</param> + /// <param name="logger">The logger.</param> + public ExternalDataManager( + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager, + IPathManager pathManager, + ITrickplayManager trickplayManager, + ILogger<ExternalDataManager> logger) + { + _keyframeManager = keyframeManager; + _mediaSegmentManager = mediaSegmentManager; + _pathManager = pathManager; + _trickplayManager = trickplayManager; + _logger = logger; + } + + /// <inheritdoc/> + public async Task DeleteExternalItemDataAsync(BaseItem item, CancellationToken cancellationToken) + { + var validPaths = _pathManager.GetExtractedDataPaths(item).Where(Directory.Exists).ToList(); + var itemId = item.Id; + if (validPaths.Count > 0) + { + foreach (var path in validPaths) + { + try + { + Directory.Delete(path, true); + } + catch (Exception ex) + { + _logger.LogWarning("Unable to prune external item data at {Path}: {Exception}", path, ex); + } + } + } + + await _keyframeManager.DeleteKeyframeDataAsync(itemId, cancellationToken).ConfigureAwait(false); + await _mediaSegmentManager.DeleteSegmentsAsync(itemId, cancellationToken).ConfigureAwait(false); + await _trickplayManager.DeleteTrickplayDataAsync(itemId, cancellationToken).ConfigureAwait(false); + } +} diff --git a/Emby.Server.Implementations/Library/KeyframeManager.cs b/Emby.Server.Implementations/Library/KeyframeManager.cs new file mode 100644 index 0000000000..18f4ce0479 --- /dev/null +++ b/Emby.Server.Implementations/Library/KeyframeManager.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.MediaEncoding.Keyframes; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Persistence; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// Manager for Keyframe data. +/// </summary> +public class KeyframeManager : IKeyframeManager +{ + private readonly IKeyframeRepository _repository; + + /// <summary> + /// Initializes a new instance of the <see cref="KeyframeManager"/> class. + /// </summary> + /// <param name="repository">The keyframe repository.</param> + public KeyframeManager(IKeyframeRepository repository) + { + _repository = repository; + } + + /// <inheritdoc /> + public IReadOnlyList<KeyframeData> GetKeyframeData(Guid itemId) + { + return _repository.GetKeyframeData(itemId); + } + + /// <inheritdoc /> + public async Task SaveKeyframeDataAsync(Guid itemId, KeyframeData data, CancellationToken cancellationToken) + { + await _repository.SaveKeyframeDataAsync(itemId, data, cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken) + { + await _repository.DeleteKeyframeDataAsync(itemId, cancellationToken).ConfigureAwait(false); + } +} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 64a96c4e5a..df71868b6a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -34,10 +34,12 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; +using MediaBrowser.Controller.Trickplay; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; @@ -66,11 +68,11 @@ namespace Emby.Server.Implementations.Library private readonly ILogger<LibraryManager> _logger; private readonly ITaskManager _taskManager; private readonly IUserManager _userManager; - private readonly IUserDataManager _userDataRepository; + private readonly IUserDataManager _userDataManager; private readonly IServerConfigurationManager _configurationManager; private readonly Lazy<ILibraryMonitor> _libraryMonitorFactory; private readonly Lazy<IProviderManager> _providerManagerFactory; - private readonly Lazy<IUserViewManager> _userviewManagerFactory; + private readonly Lazy<IUserViewManager> _userViewManagerFactory; private readonly IServerApplicationHost _appHost; private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; @@ -106,11 +108,11 @@ namespace Emby.Server.Implementations.Library /// <param name="taskManager">The task manager.</param> /// <param name="userManager">The user manager.</param> /// <param name="configurationManager">The configuration manager.</param> - /// <param name="userDataRepository">The user data repository.</param> + /// <param name="userDataManager">The user data manager.</param> /// <param name="libraryMonitorFactory">The library monitor.</param> /// <param name="fileSystem">The file system.</param> /// <param name="providerManagerFactory">The provider manager.</param> - /// <param name="userviewManagerFactory">The userview manager.</param> + /// <param name="userViewManagerFactory">The user view manager.</param> /// <param name="mediaEncoder">The media encoder.</param> /// <param name="itemRepository">The item repository.</param> /// <param name="imageProcessor">The image processor.</param> @@ -124,11 +126,11 @@ namespace Emby.Server.Implementations.Library ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, - IUserDataManager userDataRepository, + IUserDataManager userDataManager, Lazy<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem, Lazy<IProviderManager> providerManagerFactory, - Lazy<IUserViewManager> userviewManagerFactory, + Lazy<IUserViewManager> userViewManagerFactory, IMediaEncoder mediaEncoder, IItemRepository itemRepository, IImageProcessor imageProcessor, @@ -142,11 +144,11 @@ namespace Emby.Server.Implementations.Library _taskManager = taskManager; _userManager = userManager; _configurationManager = configurationManager; - _userDataRepository = userDataRepository; + _userDataManager = userDataManager; _libraryMonitorFactory = libraryMonitorFactory; _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; - _userviewManagerFactory = userviewManagerFactory; + _userViewManagerFactory = userViewManagerFactory; _mediaEncoder = mediaEncoder; _itemRepository = itemRepository; _imageProcessor = imageProcessor; @@ -202,13 +204,13 @@ namespace Emby.Server.Implementations.Library private IProviderManager ProviderManager => _providerManagerFactory.Value; - private IUserViewManager UserViewManager => _userviewManagerFactory.Value; + private IUserViewManager UserViewManager => _userViewManagerFactory.Value; /// <summary> /// Gets or sets the postscan tasks. /// </summary> /// <value>The postscan tasks.</value> - private ILibraryPostScanTask[] PostscanTasks { get; set; } = []; + private ILibraryPostScanTask[] PostScanTasks { get; set; } = []; /// <summary> /// Gets or sets the intro providers. @@ -245,20 +247,20 @@ 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 post scan tasks.</param> + /// <param name="postScanTasks">The post scan tasks.</param> public void AddParts( IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, - IEnumerable<ILibraryPostScanTask> postscanTasks) + IEnumerable<ILibraryPostScanTask> postScanTasks) { EntityResolutionIgnoreRules = rules.ToArray(); EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray(); IntroProviders = introProviders.ToArray(); Comparers = itemComparers.ToArray(); - PostscanTasks = postscanTasks.ToArray(); + PostScanTasks = postScanTasks.ToArray(); } /// <summary> @@ -393,7 +395,7 @@ namespace Emby.Server.Implementations.Library } } - if (options.DeleteFileLocation && item.IsFileProtocol) + if ((options.DeleteFileLocation && item.IsFileProtocol) || IsInternalItem(item)) { // Assume only the first is required // Add this flag to GetDeletePaths if required in the future @@ -472,6 +474,36 @@ namespace Emby.Server.Implementations.Library ReportItemRemoved(item, parent); } + private bool IsInternalItem(BaseItem item) + { + if (!item.IsFileProtocol) + { + return false; + } + + var pathToCheck = item switch + { + Genre => _configurationManager.ApplicationPaths.GenrePath, + MusicArtist => _configurationManager.ApplicationPaths.ArtistsPath, + MusicGenre => _configurationManager.ApplicationPaths.GenrePath, + Person => _configurationManager.ApplicationPaths.PeoplePath, + Studio => _configurationManager.ApplicationPaths.StudioPath, + Year => _configurationManager.ApplicationPaths.YearPath, + _ => null + }; + + var itemPath = item.Path; + if (!string.IsNullOrEmpty(pathToCheck) && !string.IsNullOrEmpty(itemPath)) + { + var cleanPath = _fileSystem.GetValidFilename(itemPath); + var cleanCheckPath = _fileSystem.GetValidFilename(pathToCheck); + + return cleanPath.StartsWith(cleanCheckPath, StringComparison.Ordinal); + } + + return false; + } + private List<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) { var list = GetInternalMetadataPaths(item); @@ -639,7 +671,7 @@ namespace Emby.Server.Implementations.Library } } - // Need to remove subpaths that may have been resolved from shortcuts + // Need to remove sub-paths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action if (isPhysicalRoot) { @@ -772,11 +804,12 @@ namespace Emby.Server.Implementations.Library // Add in the plug-in folders var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists"); - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); Folder folder = new PlaylistsFolder { - Path = path + Path = path, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, }; if (folder.Id.IsEmpty()) @@ -862,7 +895,7 @@ namespace Emby.Server.Implementations.Library { Path = path, IsFolder = isFolder, - OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) }, + OrderBy = [(ItemSortBy.DateCreated, SortOrder.Descending)], Limit = 1, DtoOptions = new DtoOptions(true) }; @@ -968,7 +1001,7 @@ namespace Emby.Server.Implementations.Library { var existing = GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { BaseItemKind.MusicArtist }, + IncludeItemTypes = [BaseItemKind.MusicArtist], Name = name, DtoOptions = options }).Cast<MusicArtist>() @@ -987,12 +1020,13 @@ namespace Emby.Server.Implementations.Library var item = GetItemById(id) as T; if (item is null) { + var info = Directory.CreateDirectory(path); item = new T { Name = name, Id = id, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Path = path }; @@ -1118,7 +1152,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> private async Task RunPostScanTasks(IProgress<double> progress, CancellationToken cancellationToken) { - var tasks = PostscanTasks.ToList(); + var tasks = PostScanTasks.ToList(); var numComplete = 0; var numTasks = tasks.Count; @@ -1241,7 +1275,7 @@ namespace Emby.Server.Implementations.Library private CollectionTypeOptions? GetCollectionType(string path) { - var files = _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false); + var files = _fileSystem.GetFilePaths(path, [".collection"], true, false); foreach (ReadOnlySpan<char> file in files) { if (Enum.TryParse<CollectionTypeOptions>(Path.GetFileNameWithoutExtension(file), true, out var res)) @@ -1312,7 +1346,7 @@ namespace Emby.Server.Implementations.Library var parent = GetItemById(query.ParentId); if (parent is not null) { - SetTopParentIdsOrAncestors(query, new[] { parent }); + SetTopParentIdsOrAncestors(query, [parent]); } } @@ -1343,7 +1377,7 @@ namespace Emby.Server.Implementations.Library var parent = GetItemById(query.ParentId); if (parent is not null) { - SetTopParentIdsOrAncestors(query, new[] { parent }); + SetTopParentIdsOrAncestors(query, [parent]); } } @@ -1531,7 +1565,7 @@ namespace Emby.Server.Implementations.Library var parent = GetItemById(query.ParentId); if (parent is not null) { - SetTopParentIdsOrAncestors(query, new[] { parent }); + SetTopParentIdsOrAncestors(query, [parent]); } } @@ -1561,7 +1595,7 @@ namespace Emby.Server.Implementations.Library // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) { - query.TopParentIds = new[] { Guid.NewGuid() }; + query.TopParentIds = [Guid.NewGuid()]; } } else @@ -1572,7 +1606,7 @@ namespace Emby.Server.Implementations.Library // Prevent searching in all libraries due to empty filter if (query.AncestorIds.Length == 0) { - query.AncestorIds = new[] { Guid.NewGuid() }; + query.AncestorIds = [Guid.NewGuid()]; } } @@ -1601,7 +1635,7 @@ namespace Emby.Server.Implementations.Library // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) { - query.TopParentIds = new[] { Guid.NewGuid() }; + query.TopParentIds = [Guid.NewGuid()]; } } } @@ -1612,7 +1646,7 @@ namespace Emby.Server.Implementations.Library { if (view.ViewType == CollectionType.livetv) { - return new[] { view.Id }; + return [view.Id]; } // Translate view into folders @@ -1661,7 +1695,7 @@ namespace Emby.Server.Implementations.Library var topParent = item.GetTopParent(); if (topParent is not null) { - return new[] { topParent.Id }; + return [topParent.Id]; } return []; @@ -1857,7 +1891,7 @@ namespace Emby.Server.Implementations.Library userComparer.User = user; userComparer.UserManager = _userManager; - userComparer.UserDataRepository = _userDataRepository; + userComparer.UserDataManager = _userDataManager; return userComparer; } @@ -1868,7 +1902,7 @@ namespace Emby.Server.Implementations.Library /// <inheritdoc /> public void CreateItem(BaseItem item, BaseItem? parent) { - CreateItems(new[] { item }, parent, CancellationToken.None); + CreateItems([item], parent, CancellationToken.None); } /// <inheritdoc /> @@ -1920,7 +1954,7 @@ namespace Emby.Server.Implementations.Library try { - return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified; + return image.DateModified.Subtract(_fileSystem.GetLastWriteTimeUtc(image.Path)).Duration().TotalSeconds > 1; } catch (Exception ex) { @@ -1947,6 +1981,8 @@ namespace Emby.Server.Implementations.Library return; } + var anyChange = false; + foreach (var img in outdated) { var image = img; @@ -1978,6 +2014,7 @@ namespace Emby.Server.Implementations.Library try { size = _imageProcessor.GetImageDimensions(item, image); + anyChange = image.Width != size.Width || image.Height != size.Height; image.Width = size.Width; image.Height = size.Height; } @@ -1985,23 +2022,29 @@ namespace Emby.Server.Implementations.Library { _logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path); size = default; + anyChange = image.Width != size.Width || image.Height != size.Height; image.Width = 0; image.Height = 0; } try { - image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path, size); + var blurhash = _imageProcessor.GetImageBlurHash(image.Path, size); + anyChange = anyChange || !blurhash.Equals(image.BlurHash, StringComparison.Ordinal); + image.BlurHash = blurhash; } catch (Exception ex) { _logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path); + anyChange = anyChange || !string.IsNullOrEmpty(image.BlurHash); image.BlurHash = string.Empty; } try { - image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path); + var modifiedDate = _fileSystem.GetLastWriteTimeUtc(image.Path); + anyChange = anyChange || modifiedDate != image.DateModified; + image.DateModified = modifiedDate; } catch (Exception ex) { @@ -2009,20 +2052,28 @@ namespace Emby.Server.Implementations.Library } } - _itemRepository.SaveImages(item); + if (anyChange) + { + _itemRepository.SaveImages(item); + } + RegisterItem(item); } /// <inheritdoc /> public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - _itemRepository.SaveItems(items, cancellationToken); - foreach (var item in items) { + item.DateLastSaved = DateTime.UtcNow; await RunMetadataSavers(item, updateReason).ConfigureAwait(false); + + // Modify again, so saved value is after write time of externally saved metadata + item.DateLastSaved = DateTime.UtcNow; } + _itemRepository.SaveItems(items, cancellationToken); + if (ItemUpdated is not null) { foreach (var item in items) @@ -2054,7 +2105,7 @@ namespace Emby.Server.Implementations.Library /// <inheritdoc /> public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) - => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken); + => UpdateItemsAsync([item], parent, updateReason, cancellationToken); public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) { @@ -2063,8 +2114,6 @@ namespace Emby.Server.Implementations.Library await ProviderManager.SaveMetadataAsync(item, updateReason).ConfigureAwait(false); } - item.DateLastSaved = DateTime.UtcNow; - await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false); } @@ -2283,13 +2332,13 @@ namespace Emby.Server.Implementations.Library if (item is null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase)) { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); item = new UserView { Path = path, Id = id, - DateCreated = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Name = name, ViewType = viewType, ForcedSortName = sortName @@ -2331,13 +2380,13 @@ namespace Emby.Server.Implementations.Library if (item is null) { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); item = new UserView { Path = path, Id = id, - DateCreated = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Name = name, ViewType = viewType, ForcedSortName = sortName, @@ -2350,12 +2399,13 @@ namespace Emby.Server.Implementations.Library isNew = true; } - var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; + var lastRefreshedUtc = item.DateLastRefreshed; + var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval; if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); - refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; + refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc; } if (refresh) @@ -2395,31 +2445,31 @@ namespace Emby.Server.Implementations.Library if (item is null) { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); item = new UserView { Path = path, Id = id, - DateCreated = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Name = name, ViewType = viewType, - ForcedSortName = sortName + ForcedSortName = sortName, + DisplayParentId = parentId }; - item.DisplayParentId = parentId; - CreateItem(item, null); isNew = true; } - var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; + var lastRefreshedUtc = item.DateLastRefreshed; + var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval; if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); - refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; + refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc; } if (refresh) @@ -2465,20 +2515,19 @@ namespace Emby.Server.Implementations.Library if (item is null) { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); item = new UserView { Path = path, Id = id, - DateCreated = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Name = name, ViewType = viewType, - ForcedSortName = sortName + ForcedSortName = sortName, + DisplayParentId = parentId }; - item.DisplayParentId = parentId; - CreateItem(item, null); isNew = true; @@ -2490,12 +2539,13 @@ namespace Emby.Server.Implementations.Library item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); } - var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; + var lastRefreshedUtc = item.DateLastRefreshed; + var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval; if (!refresh && !item.DisplayParentId.IsEmpty()) { var displayParent = GetItemById(item.DisplayParentId); - refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; + refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc; } if (refresh) @@ -2556,7 +2606,6 @@ namespace Emby.Server.Implementations.Library var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; - // TODO nullable - what are we trying to do there with empty episodeInfo? EpisodeInfo? episodeInfo = null; if (episode.IsFileProtocol) { @@ -2574,44 +2623,12 @@ namespace Emby.Server.Implementations.Library } } - episodeInfo ??= new EpisodeInfo(episode.Path); - - try - { - var libraryOptions = GetLibraryOptions(episode); - if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase)) - { - // Read from metadata - var mediaInfo = _mediaEncoder.GetMediaInfo( - new MediaInfoRequest - { - MediaSource = episode.GetMediaSources(false)[0], - MediaType = DlnaProfileType.Video - }, - CancellationToken.None).GetAwaiter().GetResult(); - if (mediaInfo.ParentIndexNumber > 0) - { - episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber; - } - - if (mediaInfo.IndexNumber > 0) - { - episodeInfo.EpisodeNumber = mediaInfo.IndexNumber; - } - - if (!string.IsNullOrEmpty(mediaInfo.ShowName)) - { - episodeInfo.SeriesName = mediaInfo.ShowName; - } - } - } - catch (Exception ex) + var changed = false; + if (episodeInfo is null) { - _logger.LogError(ex, "Error reading the episode information with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path); + return changed; } - var changed = false; - if (episodeInfo.IsByDate) { if (episode.IndexNumber.HasValue) @@ -2945,7 +2962,7 @@ namespace Emby.Server.Implementations.Library { var path = Path.Combine(virtualFolderPath, collectionType.ToString()!.ToLowerInvariant() + ".collection"); // Can't be null with legal values? - await File.WriteAllBytesAsync(path, []).ConfigureAwait(false); + FileHelper.CreateEmpty(path); } CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); @@ -2988,19 +3005,28 @@ namespace Emby.Server.Implementations.Library if (personEntity is null) { - var path = Person.GetPath(person.Name); - personEntity = new Person() + try { - Name = person.Name, - Id = GetItemByNameId<Person>(path), - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - Path = path - }; - - personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey(); - saveEntity = true; - createEntity = true; + var path = Person.GetPath(person.Name); + var info = Directory.CreateDirectory(path); + personEntity = new Person() + { + Name = person.Name, + Id = GetItemByNameId<Person>(path), + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, + Path = path + }; + + personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey(); + saveEntity = true; + createEntity = true; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to create person {Name}", person.Name); + continue; + } } foreach (var id in person.ProviderIds) @@ -3034,6 +3060,8 @@ namespace Emby.Server.Implementations.Library } await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); + personEntity.DateLastSaved = DateTime.UtcNow; + CreateItems([personEntity], null, CancellationToken.None); } } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index c6cfd5391a..1e3b8ea760 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -379,7 +379,7 @@ namespace Emby.Server.Implementations.Library var culture = _localizationManager.FindLanguageInfo(language); if (culture is not null) { - return culture.ThreeLetterISOLanguageNames; + return culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) ? [culture.Name] : culture.ThreeLetterISOLanguageNames; } return [language]; @@ -681,17 +681,17 @@ namespace Emby.Server.Implementations.Library mediaInfo = await _mediaEncoder.GetMediaInfo( new MediaInfoRequest - { - MediaSource = mediaSource, - MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, - ExtractChapters = false - }, + { + MediaSource = mediaSource, + MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, + ExtractChapters = false + }, cancellationToken).ConfigureAwait(false); if (cacheFilePath is not null) { Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); - FileStream createStream = File.Create(cacheFilePath); + FileStream createStream = AsyncFile.Create(cacheFilePath); await using (createStream.ConfigureAwait(false)) { await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Library/PathManager.cs b/Emby.Server.Implementations/Library/PathManager.cs index dbd2333ff3..a9b7a1274b 100644 --- a/Emby.Server.Implementations/Library/PathManager.cs +++ b/Emby.Server.Implementations/Library/PathManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using MediaBrowser.Common.Configuration; @@ -29,14 +30,14 @@ public class PathManager : IPathManager _appPaths = appPaths; } - private string SubtitleCachePath => Path.Join(_appPaths.DataPath, "subtitles"); + private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles"); - private string AttachmentCachePath => Path.Join(_appPaths.DataPath, "attachments"); + private string AttachmentCachePath => Path.Combine(_appPaths.DataPath, "attachments"); /// <inheritdoc /> public string GetAttachmentPath(string mediaSourceId, string fileName) { - return Path.Join(GetAttachmentFolderPath(mediaSourceId), fileName); + return Path.Combine(GetAttachmentFolderPath(mediaSourceId), fileName); } /// <inheritdoc /> @@ -58,7 +59,7 @@ public class PathManager : IPathManager /// <inheritdoc /> public string GetSubtitlePath(string mediaSourceId, int streamIndex, string extension) { - return Path.Join(GetSubtitleFolderPath(mediaSourceId), streamIndex.ToString(CultureInfo.InvariantCulture) + extension); + return Path.Combine(GetSubtitleFolderPath(mediaSourceId), streamIndex.ToString(CultureInfo.InvariantCulture) + extension); } /// <inheritdoc /> @@ -67,14 +68,14 @@ public class PathManager : IPathManager var id = item.Id.ToString("D", CultureInfo.InvariantCulture).AsSpan(); return saveWithMedia - ? Path.Join(item.ContainingFolderPath, Path.ChangeExtension(item.Path, ".trickplay")) + ? Path.Combine(item.ContainingFolderPath, Path.ChangeExtension(Path.GetFileName(item.Path), ".trickplay")) : Path.Join(_config.ApplicationPaths.TrickplayPath, id[..2], id); } /// <inheritdoc/> public string GetChapterImageFolderPath(BaseItem item) { - return Path.Join(item.GetInternalMetadataPath(), "chapters"); + return Path.Combine(item.GetInternalMetadataPath(), "chapters"); } /// <inheritdoc/> @@ -82,6 +83,19 @@ public class PathManager : IPathManager { var filename = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + "_" + chapterPositionTicks.ToString(CultureInfo.InvariantCulture) + ".jpg"; - return Path.Join(GetChapterImageFolderPath(item), filename); + return Path.Combine(GetChapterImageFolderPath(item), filename); + } + + /// <inheritdoc/> + public IReadOnlyList<string> GetExtractedDataPaths(BaseItem item) + { + var mediaSourceId = item.Id.ToString("N", CultureInfo.InvariantCulture); + return [ + GetAttachmentFolderPath(mediaSourceId), + GetSubtitleFolderPath(mediaSourceId), + GetTrickplayDirectory(item, false), + GetTrickplayDirectory(item, true), + GetChapterImageFolderPath(item) + ]; } } diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index c9e3a4daf8..06aa772bd9 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -136,23 +136,33 @@ namespace Emby.Server.Implementations.Library if (config.UseFileCreationTimeForDateAdded) { - // directoryService.getFile may return null - if (info is not null) + var fileCreationDate = info?.CreationTimeUtc; + if (fileCreationDate is not null) { - var dateCreated = info.CreationTimeUtc; - - if (dateCreated.Equals(DateTime.MinValue)) + var dateCreated = fileCreationDate; + if (dateCreated == DateTime.MinValue) { dateCreated = DateTime.UtcNow; } - item.DateCreated = dateCreated; + item.DateCreated = dateCreated.Value; } } else { item.DateCreated = DateTime.UtcNow; } + + if (info is not null && !info.IsDirectory) + { + item.Size = info.Length; + } + + var fileModificationDate = info?.LastWriteTimeUtc; + if (fileModificationDate.HasValue) + { + item.DateModified = fileModificationDate.Value; + } } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index f1aeb1340a..b2ceee97d8 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -456,12 +456,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies { var videoPath = result.Items[0].Path; var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name)); + var hasOtherSubfolders = multiDiscFolders.Count > 0; - if (!hasPhotos) + if (!hasPhotos && !hasOtherSubfolders) { var movie = (T)result.Items[0]; movie.IsInMixedFolder = false; - movie.Name = Path.GetFileName(movie.ContainingFolderPath); + if (collectionType == CollectionType.movies || collectionType is null) + { + movie.Name = Path.GetFileName(movie.ContainingFolderPath); + } + return movie; } } diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs index d51f9aaa79..a31d5eccac 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs @@ -5,45 +5,44 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class ArtistsPostScanTask. +/// </summary> +public class ArtistsPostScanTask : ILibraryPostScanTask { /// <summary> - /// Class ArtistsPostScanTask. + /// The _library manager. /// </summary> - public class ArtistsPostScanTask : ILibraryPostScanTask - { - /// <summary> - /// The _library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; - private readonly ILogger<ArtistsValidator> _logger; - private readonly IItemRepository _itemRepo; + private readonly ILibraryManager _libraryManager; + private readonly ILogger<ArtistsValidator> _logger; + private readonly IItemRepository _itemRepo; - /// <summary> - /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="itemRepo">The item repository.</param> - public ArtistsPostScanTask( - ILibraryManager libraryManager, - ILogger<ArtistsValidator> logger, - IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public ArtistsPostScanTask( + ILibraryManager libraryManager, + ILogger<ArtistsValidator> logger, + IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); - } + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 7591e8391f..7cc851b73b 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -10,102 +10,101 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class ArtistsValidator. +/// </summary> +public class ArtistsValidator { /// <summary> - /// Class ArtistsValidator. + /// The library manager. /// </summary> - public class ArtistsValidator - { - /// <summary> - /// The library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; + private readonly ILibraryManager _libraryManager; - /// <summary> - /// The logger. - /// </summary> - private readonly ILogger<ArtistsValidator> _logger; - private readonly IItemRepository _itemRepo; + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<ArtistsValidator> _logger; + private readonly IItemRepository _itemRepo; - /// <summary> - /// Initializes a new instance of the <see cref="ArtistsValidator" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="itemRepo">The item repository.</param> - public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsValidator" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - var names = _itemRepo.GetAllArtistNames(); + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var names = _itemRepo.GetAllArtistNames(); - var numComplete = 0; - var count = names.Count; + var numComplete = 0; + var count = names.Count; - foreach (var name in names) + foreach (var name in names) + { + try { - try - { - var item = _libraryManager.GetArtist(name); - - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // Don't clutter the log - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error refreshing {ArtistName}", name); - } - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 100; + var item = _libraryManager.GetArtist(name); - progress.Report(percent); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } - - var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + catch (OperationCanceledException) { - IncludeItemTypes = new[] { BaseItemKind.MusicArtist }, - IsDeadArtist = true, - IsLocked = false - }).Cast<MusicArtist>().ToList(); - - foreach (var item in deadEntities) + // Don't clutter the log + throw; + } + catch (Exception ex) { - if (!item.IsAccessedByName) - { - continue; - } + _logger.LogError(ex, "Error refreshing {ArtistName}", name); + } - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; - _libraryManager.DeleteItem( - item, - new DeleteOptions - { - DeleteFileLocation = false - }, - false); + progress.Report(percent); + } + + var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.MusicArtist], + IsDeadArtist = true, + IsLocked = false + }).Cast<MusicArtist>().ToList(); + + foreach (var item in deadEntities) + { + if (!item.IsAccessedByName) + { + continue; } - progress.Report(100); + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); + + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false + }, + false); } + + progress.Report(100); } } diff --git a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs index 337b1afdd4..e62c638ed6 100644 --- a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs @@ -9,149 +9,145 @@ using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class CollectionPostScanTask. +/// </summary> +public class CollectionPostScanTask : ILibraryPostScanTask { + private readonly ILibraryManager _libraryManager; + private readonly ICollectionManager _collectionManager; + private readonly ILogger<CollectionPostScanTask> _logger; + /// <summary> - /// Class CollectionPostScanTask. + /// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class. /// </summary> - public class CollectionPostScanTask : ILibraryPostScanTask + /// <param name="libraryManager">The library manager.</param> + /// <param name="collectionManager">The collection manager.</param> + /// <param name="logger">The logger.</param> + public CollectionPostScanTask( + ILibraryManager libraryManager, + ICollectionManager collectionManager, + ILogger<CollectionPostScanTask> logger) { - private readonly ILibraryManager _libraryManager; - private readonly ICollectionManager _collectionManager; - private readonly ILogger<CollectionPostScanTask> _logger; - - /// <summary> - /// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="collectionManager">The collection manager.</param> - /// <param name="logger">The logger.</param> - public CollectionPostScanTask( - ILibraryManager libraryManager, - ICollectionManager collectionManager, - ILogger<CollectionPostScanTask> logger) - { - _libraryManager = libraryManager; - _collectionManager = collectionManager; - _logger = logger; - } + _libraryManager = libraryManager; + _collectionManager = collectionManager; + _logger = logger; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>(); + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>(); - foreach (var library in _libraryManager.RootFolder.Children) + foreach (var library in _libraryManager.RootFolder.Children) + { + if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection) { - if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection) - { - continue; - } + continue; + } - var startIndex = 0; - var pagesize = 1000; + var startIndex = 0; + var pagesize = 1000; - while (true) + while (true) + { + var movies = _libraryManager.GetItemList(new InternalItemsQuery { - var movies = _libraryManager.GetItemList(new InternalItemsQuery - { - MediaTypes = new[] { MediaType.Video }, - IncludeItemTypes = new[] { BaseItemKind.Movie }, - IsVirtualItem = false, - OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, - Parent = library, - StartIndex = startIndex, - Limit = pagesize, - Recursive = true - }); - - foreach (var m in movies) + MediaTypes = [MediaType.Video], + IncludeItemTypes = [BaseItemKind.Movie], + IsVirtualItem = false, + OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending)], + Parent = library, + StartIndex = startIndex, + Limit = pagesize, + Recursive = true + }); + + foreach (var m in movies) + { + if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName)) { - if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName)) + if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList)) { - if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList)) - { - movieList.Add(movie.Id); - } - else - { - collectionNameMoviesMap[movie.CollectionName] = new HashSet<Guid> { movie.Id }; - } + movieList.Add(movie.Id); + } + else + { + collectionNameMoviesMap[movie.CollectionName] = new HashSet<Guid> { movie.Id }; } } + } - if (movies.Count < pagesize) - { - break; - } - - startIndex += pagesize; + if (movies.Count < pagesize) + { + break; } + + startIndex += pagesize; } + } - var numComplete = 0; - var count = collectionNameMoviesMap.Count; + var numComplete = 0; + var count = collectionNameMoviesMap.Count; - if (count == 0) - { - progress.Report(100); - return; - } + if (count == 0) + { + progress.Report(100); + return; + } - var boxSets = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { BaseItemKind.BoxSet }, - CollapseBoxSetItems = false, - Recursive = true - }); + var boxSets = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.BoxSet], + CollapseBoxSetItems = false, + Recursive = true + }); - foreach (var (collectionName, movieIds) in collectionNameMoviesMap) + foreach (var (collectionName, movieIds) in collectionNameMoviesMap) + { + try { - try + var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet; + if (boxSet is null) { - var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet; - if (boxSet is null) + // won't automatically create collection if only one movie in it + if (movieIds.Count >= 2) { - // won't automatically create collection if only one movie in it - if (movieIds.Count >= 2) + boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions { - boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions - { - Name = collectionName, - IsLocked = true - }); + Name = collectionName, + }).ConfigureAwait(false); - await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds); - } + await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds).ConfigureAwait(false); } - else - { - await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds); - } - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 100; - - progress.Report(percent); } - catch (Exception ex) + else { - _logger.LogError(ex, "Error refreshing {CollectionName} with {@MovieIds}", collectionName, movieIds); + await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds).ConfigureAwait(false); } - } - progress.Report(100); + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing {CollectionName} with {@MovieIds}", collectionName, movieIds); + } } + + progress.Report(100); } } diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs index d21d2887b0..5097e0073d 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -5,45 +5,44 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class GenresPostScanTask. +/// </summary> +public class GenresPostScanTask : ILibraryPostScanTask { /// <summary> - /// Class GenresPostScanTask. + /// The _library manager. /// </summary> - public class GenresPostScanTask : ILibraryPostScanTask - { - /// <summary> - /// The _library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; - private readonly ILogger<GenresValidator> _logger; - private readonly IItemRepository _itemRepo; + private readonly ILibraryManager _libraryManager; + private readonly ILogger<GenresValidator> _logger; + private readonly IItemRepository _itemRepo; - /// <summary> - /// Initializes a new instance of the <see cref="GenresPostScanTask" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="itemRepo">The item repository.</param> - public GenresPostScanTask( - ILibraryManager libraryManager, - ILogger<GenresValidator> logger, - IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } + /// <summary> + /// Initializes a new instance of the <see cref="GenresPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public GenresPostScanTask( + ILibraryManager libraryManager, + ILogger<GenresValidator> logger, + IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); - } + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } diff --git a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs index 364770fcdc..fbfc9f7d54 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs @@ -8,97 +8,96 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class GenresValidator. +/// </summary> +public class GenresValidator { /// <summary> - /// Class GenresValidator. + /// The library manager. /// </summary> - public class GenresValidator - { - /// <summary> - /// The library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; - private readonly IItemRepository _itemRepo; + private readonly ILibraryManager _libraryManager; + private readonly IItemRepository _itemRepo; - /// <summary> - /// The logger. - /// </summary> - private readonly ILogger<GenresValidator> _logger; + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<GenresValidator> _logger; - /// <summary> - /// Initializes a new instance of the <see cref="GenresValidator"/> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="itemRepo">The item repository.</param> - public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } + /// <summary> + /// Initializes a new instance of the <see cref="GenresValidator"/> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - var names = _itemRepo.GetGenreNames(); + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var names = _itemRepo.GetGenreNames(); - var numComplete = 0; - var count = names.Count; + var numComplete = 0; + var count = names.Count; - foreach (var name in names) + foreach (var name in names) + { + try { - try - { - var item = _libraryManager.GetGenre(name); + var item = _libraryManager.GetGenre(name); - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // Don't clutter the log - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error refreshing {GenreName}", name); - } - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 100; - - progress.Report(percent); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } - - var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + catch (OperationCanceledException) { - IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre], - IsDeadGenre = true, - IsLocked = false - }); - - foreach (var item in deadEntities) + // Don't clutter the log + throw; + } + catch (Exception ex) { - _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); - - _libraryManager.DeleteItem( - item, - new DeleteOptions - { - DeleteFileLocation = false - }, - false); + _logger.LogError(ex, "Error refreshing {GenreName}", name); } - progress.Report(100); + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); } + + var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre], + IsDeadGenre = true, + IsLocked = false + }); + + foreach (var item in deadEntities) + { + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); + + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false + }, + false); + } + + progress.Report(100); } } diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs index be119866b1..76658a81b5 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -5,45 +5,44 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class MusicGenresPostScanTask. +/// </summary> +public class MusicGenresPostScanTask : ILibraryPostScanTask { /// <summary> - /// Class MusicGenresPostScanTask. + /// The library manager. /// </summary> - public class MusicGenresPostScanTask : ILibraryPostScanTask - { - /// <summary> - /// The library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; - private readonly ILogger<MusicGenresValidator> _logger; - private readonly IItemRepository _itemRepo; + private readonly ILibraryManager _libraryManager; + private readonly ILogger<MusicGenresValidator> _logger; + private readonly IItemRepository _itemRepo; - /// <summary> - /// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="itemRepo">The item repository.</param> - public MusicGenresPostScanTask( - ILibraryManager libraryManager, - ILogger<MusicGenresValidator> logger, - IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } + /// <summary> + /// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public MusicGenresPostScanTask( + ILibraryManager libraryManager, + ILogger<MusicGenresValidator> logger, + IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); - } + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 1ecf4c87c9..6203bce2bc 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -5,77 +5,76 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class MusicGenresValidator. +/// </summary> +public class MusicGenresValidator { /// <summary> - /// Class MusicGenresValidator. + /// The library manager. /// </summary> - public class MusicGenresValidator - { - /// <summary> - /// The library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; + private readonly ILibraryManager _libraryManager; - /// <summary> - /// The logger. - /// </summary> - private readonly ILogger<MusicGenresValidator> _logger; - private readonly IItemRepository _itemRepo; + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<MusicGenresValidator> _logger; + private readonly IItemRepository _itemRepo; - /// <summary> - /// Initializes a new instance of the <see cref="MusicGenresValidator" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="itemRepo">The item repository.</param> - public MusicGenresValidator(ILibraryManager libraryManager, ILogger<MusicGenresValidator> logger, IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } + /// <summary> + /// Initializes a new instance of the <see cref="MusicGenresValidator" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public MusicGenresValidator(ILibraryManager libraryManager, ILogger<MusicGenresValidator> logger, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - var names = _itemRepo.GetMusicGenreNames(); + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var names = _itemRepo.GetMusicGenreNames(); - var numComplete = 0; - var count = names.Count; + var numComplete = 0; + var count = names.Count; - foreach (var name in names) + foreach (var name in names) + { + try { - try - { - var item = _libraryManager.GetMusicGenre(name); + var item = _libraryManager.GetMusicGenre(name); - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // Don't clutter the log - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error refreshing {GenreName}", name); - } - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 100; - - progress.Report(percent); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Don't clutter the log + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing {GenreName}", name); } - progress.Report(100); + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); } + + progress.Report(100); } } diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index 725b8f76c7..b7fd24fa5c 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -9,119 +9,114 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class PeopleValidator. +/// </summary> +public class PeopleValidator { /// <summary> - /// Class PeopleValidator. + /// The _library manager. /// </summary> - public class PeopleValidator + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The _logger. + /// </summary> + private readonly ILogger _logger; + + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="PeopleValidator" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="fileSystem">The file system.</param> + public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem) { - /// <summary> - /// The _library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; - - /// <summary> - /// The _logger. - /// </summary> - private readonly ILogger _logger; - - private readonly IFileSystem _fileSystem; - - /// <summary> - /// Initializes a new instance of the <see cref="PeopleValidator" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="fileSystem">The file system.</param> - public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem) - { - _libraryManager = libraryManager; - _logger = logger; - _fileSystem = fileSystem; - } + _libraryManager = libraryManager; + _logger = logger; + _fileSystem = fileSystem; + } - /// <summary> - /// Validates the people. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) - { - var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery()); + /// <summary> + /// Validates the people. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) + { + var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery()); - var numComplete = 0; + var numComplete = 0; - var numPeople = people.Count; + var numPeople = people.Count; - _logger.LogDebug("Will refresh {0} people", numPeople); + _logger.LogDebug("Will refresh {Amount} people", numPeople); - foreach (var person in people) - { - cancellationToken.ThrowIfCancellationRequested(); + foreach (var person in people) + { + cancellationToken.ThrowIfCancellationRequested(); - try - { - var item = _libraryManager.GetPerson(person); - if (item is null) - { - _logger.LogWarning("Failed to get person: {Name}", person); - continue; - } - - var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - ImageRefreshMode = MetadataRefreshMode.ValidationOnly, - MetadataRefreshMode = MetadataRefreshMode.ValidationOnly - }; - - await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) + try + { + var item = _libraryManager.GetPerson(person); + if (item is null) { - _logger.LogError(ex, "Error validating IBN entry {Person}", person); + _logger.LogWarning("Failed to get person: {Name}", person); + continue; } - // Update progress - numComplete++; - double percent = numComplete; - percent /= numPeople; + var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ImageRefreshMode = MetadataRefreshMode.ValidationOnly, + MetadataRefreshMode = MetadataRefreshMode.ValidationOnly + }; - progress.Report(100 * percent); + await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); } - - var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + catch (OperationCanceledException) { - IncludeItemTypes = [BaseItemKind.Person], - IsDeadPerson = true, - IsLocked = false - }); - - foreach (var item in deadEntities) + throw; + } + catch (Exception ex) { - _logger.LogInformation( - "Deleting dead {2} {0} {1}.", - item.Id.ToString("N", CultureInfo.InvariantCulture), - item.Name, - item.GetType().Name); - - _libraryManager.DeleteItem( - item, - new DeleteOptions - { - DeleteFileLocation = false - }, - false); + _logger.LogError(ex, "Error validating IBN entry {Person}", person); } - progress.Report(100); + // Update progress + numComplete++; + double percent = numComplete; + percent /= numPeople; - _logger.LogInformation("People validation complete"); + progress.Report(100 * percent); } + + var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Person], + IsDeadPerson = true, + IsLocked = false + }); + + foreach (var item in deadEntities) + { + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); + + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false + }, + false); + } + + progress.Report(100); + + _logger.LogInformation("People validation complete"); } } diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs index c682b156b8..67c56c104d 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -5,46 +5,45 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class MusicGenresPostScanTask. +/// </summary> +public class StudiosPostScanTask : ILibraryPostScanTask { /// <summary> - /// Class MusicGenresPostScanTask. + /// The _library manager. /// </summary> - public class StudiosPostScanTask : ILibraryPostScanTask - { - /// <summary> - /// The _library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; + private readonly ILibraryManager _libraryManager; - private readonly ILogger<StudiosValidator> _logger; - private readonly IItemRepository _itemRepo; + private readonly ILogger<StudiosValidator> _logger; + private readonly IItemRepository _itemRepo; - /// <summary> - /// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="itemRepo">The item repository.</param> - public StudiosPostScanTask( - ILibraryManager libraryManager, - ILogger<StudiosValidator> logger, - IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } + /// <summary> + /// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public StudiosPostScanTask( + ILibraryManager libraryManager, + ILogger<StudiosValidator> logger, + IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); - } + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 26bc49c1f0..5b87e4d9d0 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -8,98 +8,97 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.Library.Validators +namespace Emby.Server.Implementations.Library.Validators; + +/// <summary> +/// Class StudiosValidator. +/// </summary> +public class StudiosValidator { /// <summary> - /// Class StudiosValidator. + /// The library manager. /// </summary> - public class StudiosValidator - { - /// <summary> - /// The library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; + private readonly ILibraryManager _libraryManager; - private readonly IItemRepository _itemRepo; + private readonly IItemRepository _itemRepo; - /// <summary> - /// The logger. - /// </summary> - private readonly ILogger<StudiosValidator> _logger; + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger<StudiosValidator> _logger; - /// <summary> - /// Initializes a new instance of the <see cref="StudiosValidator" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="logger">The logger.</param> - /// <param name="itemRepo">The item repository.</param> - public StudiosValidator(ILibraryManager libraryManager, ILogger<StudiosValidator> logger, IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } + /// <summary> + /// Initializes a new instance of the <see cref="StudiosValidator" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> + public StudiosValidator(ILibraryManager libraryManager, ILogger<StudiosValidator> logger, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _itemRepo = itemRepo; + } - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - var names = _itemRepo.GetStudioNames(); + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var names = _itemRepo.GetStudioNames(); - var numComplete = 0; - var count = names.Count; + var numComplete = 0; + var count = names.Count; - foreach (var name in names) + foreach (var name in names) + { + try { - try - { - var item = _libraryManager.GetStudio(name); + var item = _libraryManager.GetStudio(name); - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // Don't clutter the log - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error refreshing {StudioName}", name); - } - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 100; - - progress.Report(percent); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } - - var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + catch (OperationCanceledException) { - IncludeItemTypes = new[] { BaseItemKind.Studio }, - IsDeadStudio = true, - IsLocked = false - }); - - foreach (var item in deadEntities) + // Don't clutter the log + throw; + } + catch (Exception ex) { - _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); - - _libraryManager.DeleteItem( - item, - new DeleteOptions - { - DeleteFileLocation = false - }, - false); + _logger.LogError(ex, "Error refreshing {StudioName}", name); } - progress.Report(100); + numComplete++; + double percent = numComplete; + percent /= count; + percent *= 100; + + progress.Report(percent); } + + var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = [BaseItemKind.Studio], + IsDeadStudio = true, + IsLocked = false + }); + + foreach (var item in deadEntities) + { + _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); + + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false + }, + false); + } + + progress.Report(100); } } |
