aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers/Manager
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers/Manager')
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs85
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs23
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs311
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs91
4 files changed, 305 insertions, 205 deletions
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index d82716831..9a676cb2e 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
@@ -100,8 +101,8 @@ namespace MediaBrowser.Providers.Manager
{
saveLocally = false;
- // If season is virtual under a physical series, save locally if using compatible convention
- if (item is Season season && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible)
+ // If season is virtual under a physical series, save locally
+ if (item is Season season)
{
var series = season.Series;
@@ -126,7 +127,11 @@ namespace MediaBrowser.Providers.Manager
var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally);
- var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
+ string[] retryPaths = [];
+ if (saveLocally)
+ {
+ retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
+ }
// If there are more than one output paths, the stream will need to be seekable
if (paths.Length > 1 && !source.CanSeek)
@@ -183,6 +188,29 @@ namespace MediaBrowser.Providers.Manager
try
{
_fileSystem.DeleteFile(currentPath);
+
+ // Remove local episode metadata directory if it exists and is empty
+ var directory = Path.GetDirectoryName(currentPath);
+ if (item is Episode && directory.Equals("metadata", StringComparison.Ordinal))
+ {
+ var parentDirectoryPath = Directory.GetParent(currentPath).FullName;
+ if (_fileSystem.DirectoryExists(parentDirectoryPath) && !_fileSystem.GetFiles(parentDirectoryPath).Any())
+ {
+ try
+ {
+ _logger.LogInformation("Deleting empty local metadata folder {Folder}", parentDirectoryPath);
+ Directory.Delete(parentDirectoryPath);
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ _logger.LogError(ex, "Error deleting directory {Path}", parentDirectoryPath);
+ }
+ catch (IOException ex)
+ {
+ _logger.LogError(ex, "Error deleting directory {Path}", parentDirectoryPath);
+ }
+ }
+ }
}
catch (FileNotFoundException)
{
@@ -374,6 +402,47 @@ namespace MediaBrowser.Providers.Manager
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to determine image file extension from mime type {0}", mimeType));
}
+ if (string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
+ {
+ extension = ".jpg";
+ }
+
+ extension = extension.ToLowerInvariant();
+
+ if (type == ImageType.Primary && saveLocally)
+ {
+ if (season is not null && season.IndexNumber.HasValue)
+ {
+ var seriesFolder = season.SeriesPath;
+
+ var seasonMarker = season.IndexNumber.Value == 0
+ ? "-specials"
+ : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+
+ var imageFilename = "season" + seasonMarker + "-poster" + extension;
+
+ return Path.Combine(seriesFolder, imageFilename);
+ }
+ }
+
+ if (type == ImageType.Backdrop && saveLocally)
+ {
+ if (season is not null
+ && season.IndexNumber.HasValue
+ && (imageIndex is null || imageIndex == 0))
+ {
+ var seriesFolder = season.SeriesPath;
+
+ var seasonMarker = season.IndexNumber.Value == 0
+ ? "-specials"
+ : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+
+ var imageFilename = "season" + seasonMarker + "-fanart" + extension;
+
+ return Path.Combine(seriesFolder, imageFilename);
+ }
+ }
+
if (type == ImageType.Thumb && saveLocally)
{
if (season is not null && season.IndexNumber.HasValue)
@@ -447,20 +516,12 @@ namespace MediaBrowser.Providers.Manager
break;
}
- if (string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
- {
- extension = ".jpg";
- }
-
- extension = extension.ToLowerInvariant();
-
string path = null;
-
if (saveLocally)
{
if (type == ImageType.Primary && item is Episode)
{
- path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
+ path = Path.Combine(Path.GetDirectoryName(item.Path), filename + "-thumb" + extension);
}
else if (item.IsInMixedFolder)
{
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 1a5dbd7a5..1bb7ffcce 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
@@ -96,7 +97,7 @@ namespace MediaBrowser.Providers.Manager
public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, ImageRefreshOptions refreshOptions)
{
var hasChanges = false;
- IDirectoryService directoryService = refreshOptions?.DirectoryService;
+ var directoryService = refreshOptions?.DirectoryService;
if (item is not Photo)
{
@@ -158,7 +159,7 @@ namespace MediaBrowser.Providers.Manager
}
}
- // only delete existing multi-images if new ones were added
+ // Only delete existing multi-images if new ones were added
if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count())
{
PruneImages(item, oldBackdropImages);
@@ -359,10 +360,8 @@ namespace MediaBrowser.Providers.Manager
private void PruneImages(BaseItem item, IReadOnlyList<ItemImageInfo> images)
{
- for (var i = 0; i < images.Count; i++)
+ foreach (var image in images)
{
- var image = images[i];
-
if (image.IsLocalFile)
{
try
@@ -371,7 +370,7 @@ namespace MediaBrowser.Providers.Manager
}
catch (FileNotFoundException)
{
- // nothing to do, already gone
+ // Nothing to do, already gone
}
catch (UnauthorizedAccessException ex)
{
@@ -381,6 +380,16 @@ namespace MediaBrowser.Providers.Manager
}
item.RemoveImages(images);
+
+ // Cleanup old metadata directory for episodes if empty
+ if (item is Episode)
+ {
+ var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
+ if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDirectory).Any())
+ {
+ Directory.Delete(oldLocalMetadataDirectory);
+ }
+ }
}
/// <summary>
@@ -413,12 +422,10 @@ namespace MediaBrowser.Providers.Manager
{
var changed = item.ValidateImages();
var foundImageTypes = new List<ImageType>();
-
for (var i = 0; i < _singularImages.Length; i++)
{
var type = _singularImages[i];
var image = GetFirstLocalImageInfoByType(images, type);
-
if (image is not null)
{
var currentImage = item.GetImageInfo(type, 0);
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 6f473fc07..8af4ed2a8 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -8,7 +8,6 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -72,7 +71,7 @@ namespace MediaBrowser.Providers.Manager
}
}
- public async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
+ public virtual async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{
var itemOfType = (TItemType)item;
@@ -93,10 +92,6 @@ namespace MediaBrowser.Providers.Manager
}
}
- var localImagesFailed = false;
-
- var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
-
if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages)
{
if (ImageProvider.RemoveImages(item))
@@ -105,24 +100,35 @@ namespace MediaBrowser.Providers.Manager
}
}
- // Start by validating images
- try
+ var localImagesFailed = false;
+ var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
+
+ // Only validate already registered images if we are replacing and saving locally
+ if (item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages)
{
- // Always validate images and check for new locally stored ones.
- if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions))
- {
- updateType |= ItemUpdateType.ImageUpdate;
- }
+ item.ValidateImages();
}
- catch (Exception ex)
+ else
{
- localImagesFailed = true;
- Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
+ // Run full image validation and register new local images
+ try
+ {
+ if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions))
+ {
+ updateType |= ItemUpdateType.ImageUpdate;
+ }
+ }
+ catch (Exception ex)
+ {
+ localImagesFailed = true;
+ Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
+ }
}
var metadataResult = new MetadataResult<TItemType>
{
- Item = itemOfType
+ Item = itemOfType,
+ People = LibraryManager.GetPeople(item)
};
bool hasRefreshedMetadata = true;
@@ -154,7 +160,8 @@ namespace MediaBrowser.Providers.Manager
id.IsAutomated = refreshOptions.IsAutomated;
- var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider, cancellationToken).ConfigureAwait(false);
+ var hasMetadataSavers = ProviderManager.GetMetadataSavers(item, libraryOptions).Any();
+ var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider, hasMetadataSavers, cancellationToken).ConfigureAwait(false);
updateType |= result.UpdateType;
if (result.Failures > 0)
@@ -165,7 +172,7 @@ namespace MediaBrowser.Providers.Manager
}
// Next run remote image providers, but only if local image providers didn't throw an exception
- if (!localImagesFailed && refreshOptions.ImageRefreshMode != MetadataRefreshMode.ValidationOnly)
+ if (!localImagesFailed && refreshOptions.ImageRefreshMode > MetadataRefreshMode.ValidationOnly)
{
var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList();
@@ -243,7 +250,7 @@ namespace MediaBrowser.Providers.Manager
protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
{
- if (result.Item.SupportsPeople && result.People is not null)
+ if (result.Item.SupportsPeople)
{
var baseItem = result.Item;
@@ -399,7 +406,8 @@ namespace MediaBrowser.Providers.Manager
foreach (var child in children)
{
- if (!child.IsFolder)
+ // Exclude any folders and virtual items since they are only placeholders
+ if (!child.IsFolder && !child.IsVirtualItem)
{
var childDateCreated = child.DateCreated;
if (childDateCreated > dateLastMediaAdded)
@@ -638,6 +646,7 @@ namespace MediaBrowser.Providers.Manager
MetadataRefreshOptions options,
ICollection<IMetadataProvider> providers,
ItemImageProvider imageService,
+ bool isSavingMetadata,
CancellationToken cancellationToken)
{
var refreshResult = new RefreshResult
@@ -655,102 +664,96 @@ namespace MediaBrowser.Providers.Manager
await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
}
+ if (item.IsLocked)
+ {
+ return refreshResult;
+ }
+
var temp = new MetadataResult<TItemType>
{
Item = CreateNew()
};
temp.Item.Path = item.Path;
+ temp.Item.Id = item.Id;
+ temp.Item.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
+ temp.Item.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
- // If replacing all metadata, run internet providers first
- if (options.ReplaceAllMetadata)
- {
- var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
- .ConfigureAwait(false);
-
- refreshResult.UpdateType |= remoteResult.UpdateType;
- refreshResult.ErrorMessage = remoteResult.ErrorMessage;
- refreshResult.Failures += remoteResult.Failures;
- }
-
- var hasLocalMetadata = false;
var foundImageTypes = new List<ImageType>();
- foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
+ // Do not execute local providers if we are identifying or replacing with local metadata saving enabled
+ if (options.SearchResult is null && !(isSavingMetadata && options.ReplaceAllMetadata))
{
- var providerName = provider.GetType().Name;
- Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
-
- var itemInfo = new ItemInfo(item);
-
- try
+ foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
{
- var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken).ConfigureAwait(false);
+ var providerName = provider.GetType().Name;
+ Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
+
+ var itemInfo = new ItemInfo(item);
- if (localItem.HasMetadata)
+ try
{
- foreach (var remoteImage in localItem.RemoteImages)
+ var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken).ConfigureAwait(false);
+
+ if (localItem.HasMetadata)
{
- try
+ foreach (var remoteImage in localItem.RemoteImages)
{
- if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
- && !options.IsReplacingImage(remoteImage.Type))
+ try
{
- continue;
- }
+ if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
+ && !options.IsReplacingImage(remoteImage.Type))
+ {
+ continue;
+ }
- await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);
- refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
+ await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);
+ refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
- // remember imagetype that has just been downloaded
- foundImageTypes.Add(remoteImage.Type);
+ // remember imagetype that has just been downloaded
+ foundImageTypes.Add(remoteImage.Type);
+ }
+ catch (HttpRequestException ex)
+ {
+ Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
+ }
}
- catch (HttpRequestException ex)
+
+ if (foundImageTypes.Count > 0)
{
- Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
+ imageService.UpdateReplaceImages(options, foundImageTypes);
}
- }
- if (foundImageTypes.Count > 0)
- {
- imageService.UpdateReplaceImages(options, foundImageTypes);
- }
-
- if (imageService.MergeImages(item, localItem.Images, options))
- {
- refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
- }
+ if (imageService.MergeImages(item, localItem.Images, options))
+ {
+ refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
+ }
- MergeData(localItem, temp, Array.Empty<MetadataField>(), options.ReplaceAllMetadata, true);
- refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
+ MergeData(localItem, temp, Array.Empty<MetadataField>(), false, true);
+ refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
- // Only one local provider allowed per item
- if (item.IsLocked || localItem.Item.IsLocked || IsFullLocalMetadata(localItem.Item))
- {
- hasLocalMetadata = true;
+ break;
}
- break;
+ Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
}
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error in {Provider}", provider.Name);
- Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error in {Provider}", provider.Name);
-
- // If a local provider fails, consider that a failure
- refreshResult.ErrorMessage = ex.Message;
+ // If a local provider fails, consider that a failure
+ refreshResult.ErrorMessage = ex.Message;
+ }
}
}
- // Local metadata is king - if any is found don't run remote providers
- if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || !item.StopRefreshIfLocalMetadataFound))
+ var isLocalLocked = temp.Item.IsLocked;
+ if (!isLocalLocked && (options.ReplaceAllMetadata || options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly))
{
- var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
+ var remoteResult = await ExecuteRemoteProviders(temp, logName, false, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
.ConfigureAwait(false);
refreshResult.UpdateType |= remoteResult.UpdateType;
@@ -762,19 +765,20 @@ namespace MediaBrowser.Providers.Manager
{
if (refreshResult.UpdateType > ItemUpdateType.None)
{
- if (hasLocalMetadata)
+ if (!options.RemoveOldMetadata)
+ {
+ // Add existing metadata to provider result if it does not exist there
+ MergeData(metadata, temp, Array.Empty<MetadataField>(), false, false);
+ }
+
+ if (isLocalLocked)
{
MergeData(temp, metadata, item.LockedFields, true, true);
}
else
{
- if (!options.RemoveOldMetadata)
- {
- MergeData(metadata, temp, Array.Empty<MetadataField>(), false, false);
- }
-
- // Will always replace all metadata when Scan for new and updated files is used. Else, follow the options.
- MergeData(temp, metadata, item.LockedFields, options.MetadataRefreshMode == MetadataRefreshMode.Default || options.ReplaceAllMetadata, false);
+ var shouldReplace = options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly || options.ReplaceAllMetadata;
+ MergeData(temp, metadata, item.LockedFields, shouldReplace, true);
}
}
}
@@ -787,16 +791,6 @@ namespace MediaBrowser.Providers.Manager
return refreshResult;
}
- protected virtual bool IsFullLocalMetadata(TItemType item)
- {
- if (string.IsNullOrWhiteSpace(item.Name))
- {
- return false;
- }
-
- return true;
- }
-
private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, string logName, MetadataRefreshOptions options, RefreshResult refreshResult, CancellationToken cancellationToken)
{
Logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, logName);
@@ -821,23 +815,20 @@ namespace MediaBrowser.Providers.Manager
return new TItemType();
}
- private async Task<RefreshResult> ExecuteRemoteProviders(MetadataResult<TItemType> temp, string logName, TIdType id, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, CancellationToken cancellationToken)
+ private async Task<RefreshResult> ExecuteRemoteProviders(MetadataResult<TItemType> temp, string logName, bool replaceData, TIdType id, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, CancellationToken cancellationToken)
{
var refreshResult = new RefreshResult();
- var tmpDataMerged = false;
+ if (id is not null)
+ {
+ MergeNewData(temp.Item, id);
+ }
foreach (var provider in providers)
{
var providerName = provider.GetType().Name;
Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
- if (id is not null && !tmpDataMerged)
- {
- MergeNewData(temp.Item, id);
- tmpDataMerged = true;
- }
-
try
{
var result = await provider.GetMetadata(id, cancellationToken).ConfigureAwait(false);
@@ -846,7 +837,7 @@ namespace MediaBrowser.Providers.Manager
{
result.Provider = provider.Name;
- MergeData(result, temp, Array.Empty<MetadataField>(), false, false);
+ MergeData(result, temp, Array.Empty<MetadataField>(), replaceData, false);
MergeNewData(temp.Item, id);
refreshResult.UpdateType |= ItemUpdateType.MetadataDownload;
@@ -949,11 +940,7 @@ namespace MediaBrowser.Providers.Manager
if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
{
- // Safeguard against incoming data having an empty name
- if (!string.IsNullOrWhiteSpace(source.OriginalTitle))
- {
- target.OriginalTitle = source.OriginalTitle;
- }
+ target.OriginalTitle = source.OriginalTitle;
}
if (replaceData || !target.CommunityRating.HasValue)
@@ -1016,7 +1003,7 @@ namespace MediaBrowser.Providers.Manager
{
targetResult.People = sourceResult.People;
}
- else if (targetResult.People is not null && sourceResult.People is not null)
+ else if (sourceResult.People is not null && sourceResult.People.Count > 0)
{
MergePeople(sourceResult.People, targetResult.People);
}
@@ -1049,6 +1036,10 @@ namespace MediaBrowser.Providers.Manager
{
target.Studios = source.Studios;
}
+ else
+ {
+ target.Studios = target.Studios.Concat(source.Studios).Distinct().ToArray();
+ }
}
if (!lockedFields.Contains(MetadataField.Tags))
@@ -1057,6 +1048,10 @@ namespace MediaBrowser.Providers.Manager
{
target.Tags = source.Tags;
}
+ else
+ {
+ target.Tags = target.Tags.Concat(source.Tags).Distinct().ToArray();
+ }
}
if (!lockedFields.Contains(MetadataField.ProductionLocations))
@@ -1065,6 +1060,10 @@ namespace MediaBrowser.Providers.Manager
{
target.ProductionLocations = source.ProductionLocations;
}
+ else
+ {
+ target.ProductionLocations = target.ProductionLocations.Concat(source.ProductionLocations).Distinct().ToArray();
+ }
}
foreach (var id in source.ProviderIds)
@@ -1082,17 +1081,28 @@ namespace MediaBrowser.Providers.Manager
}
}
+ if (replaceData || !target.CriticRating.HasValue)
+ {
+ target.CriticRating = source.CriticRating;
+ }
+
+ if (replaceData || target.RemoteTrailers.Count == 0)
+ {
+ target.RemoteTrailers = source.RemoteTrailers;
+ }
+ else
+ {
+ target.RemoteTrailers = target.RemoteTrailers.Concat(source.RemoteTrailers).DistinctBy(t => t.Url).ToArray();
+ }
+
MergeAlbumArtist(source, target, replaceData);
- MergeCriticRating(source, target, replaceData);
- MergeTrailers(source, target, replaceData);
MergeVideoInfo(source, target, replaceData);
MergeDisplayOrder(source, target, replaceData);
if (replaceData || string.IsNullOrEmpty(target.ForcedSortName))
{
var forcedSortName = source.ForcedSortName;
-
- if (!string.IsNullOrWhiteSpace(forcedSortName))
+ if (!string.IsNullOrEmpty(forcedSortName))
{
target.ForcedSortName = forcedSortName;
}
@@ -1100,22 +1110,44 @@ namespace MediaBrowser.Providers.Manager
if (mergeMetadataSettings)
{
- target.LockedFields = source.LockedFields;
- target.IsLocked = source.IsLocked;
+ if (replaceData || !target.IsLocked)
+ {
+ target.IsLocked = target.IsLocked || source.IsLocked;
+ }
+
+ if (target.LockedFields.Length == 0)
+ {
+ target.LockedFields = source.LockedFields;
+ }
+ else
+ {
+ target.LockedFields = target.LockedFields.Concat(source.LockedFields).Distinct().ToArray();
+ }
- // Grab the value if it's there, but if not then don't overwrite with the default
if (source.DateCreated != default)
{
target.DateCreated = source.DateCreated;
}
- target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
- target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
+ if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataCountryCode))
+ {
+ target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
+ }
+
+ if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataLanguage))
+ {
+ target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
+ }
}
}
private static void MergePeople(List<PersonInfo> source, List<PersonInfo> target)
{
+ if (target is null)
+ {
+ target = new List<PersonInfo>();
+ }
+
foreach (var person in target)
{
var normalizedName = person.Name.RemoveDiacritics();
@@ -1144,7 +1176,6 @@ namespace MediaBrowser.Providers.Manager
if (replaceData || string.IsNullOrEmpty(targetHasDisplayOrder.DisplayOrder))
{
var displayOrder = sourceHasDisplayOrder.DisplayOrder;
-
if (!string.IsNullOrWhiteSpace(displayOrder))
{
targetHasDisplayOrder.DisplayOrder = displayOrder;
@@ -1162,22 +1193,10 @@ namespace MediaBrowser.Providers.Manager
{
targetHasAlbumArtist.AlbumArtists = sourceHasAlbumArtist.AlbumArtists;
}
- }
- }
-
- private static void MergeCriticRating(BaseItem source, BaseItem target, bool replaceData)
- {
- if (replaceData || !target.CriticRating.HasValue)
- {
- target.CriticRating = source.CriticRating;
- }
- }
-
- private static void MergeTrailers(BaseItem source, BaseItem target, bool replaceData)
- {
- if (replaceData || target.RemoteTrailers.Count == 0)
- {
- target.RemoteTrailers = source.RemoteTrailers;
+ else if (sourceHasAlbumArtist.AlbumArtists.Count > 0)
+ {
+ targetHasAlbumArtist.AlbumArtists = targetHasAlbumArtist.AlbumArtists.Concat(sourceHasAlbumArtist.AlbumArtists).Distinct().ToArray();
+ }
}
}
@@ -1185,7 +1204,7 @@ namespace MediaBrowser.Providers.Manager
{
if (source is Video sourceCast && target is Video targetCast)
{
- if (replaceData || targetCast.Video3DFormat is null)
+ if (replaceData || !targetCast.Video3DFormat.HasValue)
{
targetCast.Video3DFormat = sourceCast.Video3DFormat;
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index f34034964..60d89a51b 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -69,11 +69,12 @@ namespace MediaBrowser.Providers.Manager
o.PoolInitialFill = 1;
});
- private IImageProvider[] _imageProviders = Array.Empty<IImageProvider>();
- private IMetadataService[] _metadataServices = Array.Empty<IMetadataService>();
- private IMetadataProvider[] _metadataProviders = Array.Empty<IMetadataProvider>();
- private IMetadataSaver[] _savers = Array.Empty<IMetadataSaver>();
- private IExternalId[] _externalIds = Array.Empty<IExternalId>();
+ private IImageProvider[] _imageProviders = [];
+ private IMetadataService[] _metadataServices = [];
+ private IMetadataProvider[] _metadataProviders = [];
+ private IMetadataSaver[] _savers = [];
+ private IExternalId[] _externalIds = [];
+ private IExternalUrlProvider[] _externalUrlProviders = [];
private bool _isProcessingRefreshQueue;
private bool _disposed;
@@ -132,12 +133,14 @@ namespace MediaBrowser.Providers.Manager
IEnumerable<IMetadataService> metadataServices,
IEnumerable<IMetadataProvider> metadataProviders,
IEnumerable<IMetadataSaver> metadataSavers,
- IEnumerable<IExternalId> externalIds)
+ IEnumerable<IExternalId> externalIds,
+ IEnumerable<IExternalUrlProvider> externalUrlProviders)
{
_imageProviders = imageProviders.ToArray();
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
_metadataProviders = metadataProviders.ToArray();
_externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
+ _externalUrlProviders = externalUrlProviders.OrderBy(i => i.Name).ToArray();
_savers = metadataSavers.ToArray();
}
@@ -286,7 +289,7 @@ namespace MediaBrowser.Providers.Manager
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
- return results.SelectMany(i => i.ToList());
+ return results.SelectMany(i => i);
}
/// <summary>
@@ -418,6 +421,12 @@ namespace MediaBrowser.Providers.Manager
return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false);
}
+ /// <inheritdoc />
+ public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
+ {
+ return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false));
+ }
+
private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions, bool includeDisabled, bool forceEnableInternetMetadata)
where T : BaseItem
{
@@ -871,31 +880,35 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
{
- return GetExternalIds(item)
+#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11
+ var legacyExternalIdUrls = GetExternalIds(item)
.Select(i =>
- {
- if (string.IsNullOrEmpty(i.UrlFormatString))
{
- return null;
- }
+ var urlFormatString = i.UrlFormatString;
+ if (string.IsNullOrEmpty(urlFormatString)
+ || !item.TryGetProviderId(i.Key, out var providerId))
+ {
+ return null;
+ }
- var value = item.GetProviderId(i.Key);
+ return new ExternalUrl
+ {
+ Name = i.ProviderName,
+ Url = string.Format(
+ CultureInfo.InvariantCulture,
+ urlFormatString,
+ providerId)
+ };
+ })
+ .OfType<ExternalUrl>();
+#pragma warning restore CS0618 // Type or member is obsolete
- if (string.IsNullOrEmpty(value))
- {
- return null;
- }
+ var externalUrls = _externalUrlProviders
+ .SelectMany(p => p
+ .GetExternalUrls(item)
+ .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
- return new ExternalUrl
- {
- Name = i.ProviderName,
- Url = string.Format(
- CultureInfo.InvariantCulture,
- i.UrlFormatString,
- value)
- };
- }).Where(i => i is not null)
- .Concat(item.GetRelatedUrls())!; // We just filtered out all the nulls
+ return legacyExternalIdUrls.Concat(externalUrls).OrderBy(u => u.Name);
}
/// <inheritdoc/>
@@ -906,7 +919,9 @@ namespace MediaBrowser.Providers.Manager
name: i.ProviderName,
key: i.Key,
type: i.Type,
+#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11
urlFormatString: i.UrlFormatString));
+#pragma warning restore CS0618 // Type or member is obsolete
}
/// <inheritdoc/>
@@ -968,16 +983,13 @@ namespace MediaBrowser.Providers.Manager
var id = item.Id;
_logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
- // TODO: Need to hunt down the conditions for this happening
- _activeRefreshes.AddOrUpdate(
- id,
- _ => throw new InvalidOperationException(
- string.Format(
- CultureInfo.InvariantCulture,
- "Cannot update refresh progress of item '{0}' ({1}) because a refresh for this item is not running",
- item.GetType().Name,
- item.Id.ToString("N", CultureInfo.InvariantCulture))),
- (_, _) => progress);
+ if (!_activeRefreshes.TryGetValue(id, out var current)
+ || progress <= current
+ || !_activeRefreshes.TryUpdate(id, progress, current))
+ {
+ // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
+ return;
+ }
try
{
@@ -1106,9 +1118,10 @@ namespace MediaBrowser.Providers.Manager
var musicArtists = albums
.Select(i => i.MusicArtist)
- .Where(i => i is not null);
+ .Where(i => i is not null)
+ .Distinct();
- var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, true, cancellationToken));
+ var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, true, false, cancellationToken));
await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);