diff options
| author | Marc Brooks <IDisposable@gmail.com> | 2025-02-03 19:48:59 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-03 19:48:59 -0600 |
| commit | e8cbcde02ebd930a5eeb6c95e0875a9e30acb3e8 (patch) | |
| tree | 2ecd43f232012c8f037f4cd6fee4168e46d01aa3 /MediaBrowser.Providers/Manager | |
| parent | 6dc61a430ba3a8480399309f277e5debfd6403ba (diff) | |
| parent | d376b5fbc7cf3ae7440a606a9e885d70605956bd (diff) | |
Merge branch 'master' into sort-nfo-data
Diffstat (limited to 'MediaBrowser.Providers/Manager')
| -rw-r--r-- | MediaBrowser.Providers/Manager/ImageSaver.cs | 1 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Manager/ItemImageProvider.cs | 8 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Manager/MetadataService.cs | 98 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Manager/ProviderManager.cs | 34 |
4 files changed, 81 insertions, 60 deletions
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 9a676cb2e7..8f6aa2db37 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -291,6 +291,7 @@ namespace MediaBrowser.Providers.Manager var fileStreamOptions = AsyncFile.WriteOptions; fileStreamOptions.Mode = FileMode.Create; + fileStreamOptions.Options = FileOptions.WriteThrough; if (source.CanSeek) { fileStreamOptions.PreallocationSize = source.Length; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 36a7c2fabe..64954818a5 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -229,9 +229,7 @@ namespace MediaBrowser.Providers.Manager { var mimeType = MimeTypes.GetMimeType(response.Path); - var stream = AsyncFile.OpenRead(response.Path); - - await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false); + await _providerManager.SaveImage(item, response.Path, mimeType, imageType, null, null, cancellationToken).ConfigureAwait(false); } } @@ -387,8 +385,8 @@ namespace MediaBrowser.Providers.Manager item.RemoveImages(images); - // Cleanup old metadata directory for episodes if empty - if (item is Episode) + // Cleanup old metadata directory for episodes if empty, as long as it's not a virtual item + if (item is Episode && !item.IsVirtualItem) { var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata"); if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDirectory).Any()) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 8af4ed2a88..778fbc7125 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -74,10 +74,11 @@ namespace MediaBrowser.Providers.Manager public virtual async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { var itemOfType = (TItemType)item; - var updateType = ItemUpdateType.None; - var libraryOptions = LibraryManager.GetLibraryOptions(item); + var isFirstRefresh = item.DateLastRefreshed == default; + var hasRefreshedMetadata = true; + var hasRefreshedImages = true; var requiresRefresh = libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays; @@ -131,9 +132,10 @@ namespace MediaBrowser.Providers.Manager People = LibraryManager.GetPeople(item) }; - bool hasRefreshedMetadata = true; - bool hasRefreshedImages = true; - var isFirstRefresh = item.DateLastRefreshed == default; + var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType); + updateType |= beforeSaveResult; + + updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false); // Next run metadata providers if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) @@ -188,43 +190,43 @@ namespace MediaBrowser.Providers.Manager } } - var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType); - updateType |= beforeSaveResult; + if (hasRefreshedMetadata && hasRefreshedImages) + { + item.DateLastRefreshed = DateTime.UtcNow; + } + + updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false); + + await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false); - // Save if changes were made, or it's never been saved before - if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh) + return updateType; + + async Task<ItemUpdateType> SaveInternal(BaseItem item, MetadataRefreshOptions refreshOptions, ItemUpdateType updateType, bool isFirstRefresh, bool requiresRefresh, MetadataResult<TItemType> metadataResult, CancellationToken cancellationToken) { - if (item.IsFileProtocol) + // Save if changes were made, or it's never been saved before + if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh) { - var file = TryGetFile(item.Path, refreshOptions.DirectoryService); - if (file is not null) + if (item.IsFileProtocol) { - item.DateModified = file.LastWriteTimeUtc; + var file = TryGetFile(item.Path, refreshOptions.DirectoryService); + if (file is not null) + { + item.DateModified = file.LastWriteTimeUtc; + } } - } - // If any of these properties are set then make sure the updateType is not None, just to force everything to save - if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata) - { - updateType |= ItemUpdateType.MetadataDownload; - } + // If any of these properties are set then make sure the updateType is not None, just to force everything to save + if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata) + { + updateType |= ItemUpdateType.MetadataDownload; + } - if (hasRefreshedMetadata && hasRefreshedImages) - { - item.DateLastRefreshed = DateTime.UtcNow; - } - else - { - item.DateLastRefreshed = default; + // Save to database + await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false); } - // Save to database - await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false); + return updateType; } - - await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false); - - return updateType; } private void ApplySearchResult(ItemLookupInfo lookupInfo, RemoteSearchResult result) @@ -322,17 +324,17 @@ namespace MediaBrowser.Providers.Manager return false; } - protected virtual IList<BaseItem> GetChildrenForMetadataUpdates(TItemType item) + protected virtual IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(TItemType item) { if (item is Folder folder) { return folder.GetRecursiveChildren(); } - return Array.Empty<BaseItem>(); + return []; } - protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IReadOnlyList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) { var updateType = ItemUpdateType.None; @@ -371,7 +373,7 @@ namespace MediaBrowser.Providers.Manager return updateType; } - private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IList<BaseItem> children) + private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IReadOnlyList<BaseItem> children) { if (item is Folder folder && folder.SupportsCumulativeRunTimeTicks) { @@ -395,7 +397,7 @@ namespace MediaBrowser.Providers.Manager return ItemUpdateType.None; } - private ItemUpdateType UpdateDateLastMediaAdded(TItemType item, IList<BaseItem> children) + private ItemUpdateType UpdateDateLastMediaAdded(TItemType item, IReadOnlyList<BaseItem> children) { var updateType = ItemUpdateType.None; @@ -429,7 +431,7 @@ namespace MediaBrowser.Providers.Manager return updateType; } - private ItemUpdateType UpdatePremiereDate(TItemType item, IList<BaseItem> children) + private ItemUpdateType UpdatePremiereDate(TItemType item, IReadOnlyList<BaseItem> children) { var updateType = ItemUpdateType.None; @@ -467,7 +469,7 @@ namespace MediaBrowser.Providers.Manager return updateType; } - private ItemUpdateType UpdateGenres(TItemType item, IList<BaseItem> children) + private ItemUpdateType UpdateGenres(TItemType item, IReadOnlyList<BaseItem> children) { var updateType = ItemUpdateType.None; @@ -488,7 +490,7 @@ namespace MediaBrowser.Providers.Manager return updateType; } - private ItemUpdateType UpdateStudios(TItemType item, IList<BaseItem> children) + private ItemUpdateType UpdateStudios(TItemType item, IReadOnlyList<BaseItem> children) { var updateType = ItemUpdateType.None; @@ -509,7 +511,7 @@ namespace MediaBrowser.Providers.Manager return updateType; } - private ItemUpdateType UpdateOfficialRating(TItemType item, IList<BaseItem> children) + private ItemUpdateType UpdateOfficialRating(TItemType item, IReadOnlyList<BaseItem> children) { var updateType = ItemUpdateType.None; @@ -675,6 +677,7 @@ namespace MediaBrowser.Providers.Manager }; temp.Item.Path = item.Path; temp.Item.Id = item.Id; + temp.Item.ParentIndexNumber = item.ParentIndexNumber; temp.Item.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode; temp.Item.PreferredMetadataLanguage = item.PreferredMetadataLanguage; @@ -728,7 +731,7 @@ namespace MediaBrowser.Providers.Manager refreshResult.UpdateType |= ItemUpdateType.ImageUpdate; } - MergeData(localItem, temp, Array.Empty<MetadataField>(), false, true); + MergeData(localItem, temp, [], false, true); refreshResult.UpdateType |= ItemUpdateType.MetadataImport; break; @@ -768,7 +771,7 @@ namespace MediaBrowser.Providers.Manager if (!options.RemoveOldMetadata) { // Add existing metadata to provider result if it does not exist there - MergeData(metadata, temp, Array.Empty<MetadataField>(), false, false); + MergeData(metadata, temp, [], false, false); } if (isLocalLocked) @@ -837,7 +840,7 @@ namespace MediaBrowser.Providers.Manager { result.Provider = provider.Name; - MergeData(result, temp, Array.Empty<MetadataField>(), replaceData, false); + MergeData(result, temp, [], replaceData, false); MergeNewData(temp.Item, id); refreshResult.UpdateType |= ItemUpdateType.MetadataDownload; @@ -1141,13 +1144,8 @@ namespace MediaBrowser.Providers.Manager } } - private static void MergePeople(List<PersonInfo> source, List<PersonInfo> target) + private static void MergePeople(IReadOnlyList<PersonInfo> source, IReadOnlyList<PersonInfo> target) { - if (target is null) - { - target = new List<PersonInfo>(); - } - foreach (var person in target) { var normalizedName = person.Name.RemoveDiacritics(); diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 81a9af68be..6813cfa911 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Mime; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using AsyncKeyedLock; @@ -47,7 +48,7 @@ namespace MediaBrowser.Providers.Manager /// </summary> public class ProviderManager : IProviderManager, IDisposable { - private readonly object _refreshQueueLock = new(); + private readonly Lock _refreshQueueLock = new(); private readonly ILogger<ProviderManager> _logger; private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryMonitor _libraryMonitor; @@ -199,12 +200,20 @@ namespace MediaBrowser.Providers.Manager // TODO: Isolate this hack into the tvh plugin if (string.IsNullOrEmpty(contentType)) { + // Special case for imagecache if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase)) { contentType = MediaTypeNames.Image.Png; } else { + // Deduce content type from file extension + contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path)); + } + + // Throw if we still can't determine the content type + if (string.IsNullOrEmpty(contentType)) + { throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode); } } @@ -251,15 +260,31 @@ namespace MediaBrowser.Providers.Manager } /// <inheritdoc/> - public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken) + public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(source)) { throw new ArgumentNullException(nameof(source)); } - var fileStream = AsyncFile.OpenRead(source); - return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); + try + { + var fileStream = AsyncFile.OpenRead(source); + await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger) + .SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken) + .ConfigureAwait(false); + } + finally + { + try + { + File.Delete(source); + } + catch (Exception ex) + { + _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source); + } + } } /// <inheritdoc/> @@ -1016,7 +1041,6 @@ namespace MediaBrowser.Providers.Manager /// <inheritdoc/> public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority) { - ArgumentNullException.ThrowIfNull(itemId); if (itemId.IsEmpty()) { throw new ArgumentException("Guid can't be empty", nameof(itemId)); |
