diff options
Diffstat (limited to 'Emby.Server.Implementations')
6 files changed, 116 insertions, 55 deletions
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 0ede5665f9..295efd456c 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -4,12 +4,15 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; @@ -29,6 +32,7 @@ namespace Emby.Server.Implementations.Collections private readonly ILibraryMonitor _iLibraryMonitor; private readonly ILogger<CollectionManager> _logger; private readonly IProviderManager _providerManager; + private readonly ILinkedChildrenService _linkedChildrenService; private readonly ILocalizationManager _localizationManager; private readonly IApplicationPaths _appPaths; @@ -42,6 +46,7 @@ namespace Emby.Server.Implementations.Collections /// <param name="iLibraryMonitor">The library monitor.</param> /// <param name="loggerFactory">The logger factory.</param> /// <param name="providerManager">The provider manager.</param> + /// <param name="linkedChildrenService">The linked children service.</param> public CollectionManager( ILibraryManager libraryManager, IApplicationPaths appPaths, @@ -49,13 +54,15 @@ namespace Emby.Server.Implementations.Collections IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor, ILoggerFactory loggerFactory, - IProviderManager providerManager) + IProviderManager providerManager, + ILinkedChildrenService linkedChildrenService) { _libraryManager = libraryManager; _fileSystem = fileSystem; _iLibraryMonitor = iLibraryMonitor; _logger = loggerFactory.CreateLogger<CollectionManager>(); _providerManager = providerManager; + _linkedChildrenService = linkedChildrenService; _localizationManager = localizationManager; _appPaths = appPaths; } @@ -120,6 +127,22 @@ namespace Emby.Server.Implementations.Collections return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded); } + /// <inheritdoc /> + public IEnumerable<BoxSet> GetCollectionsContainingItem(User user, Guid itemId) + { + ArgumentNullException.ThrowIfNull(user); + + if (itemId.IsEmpty()) + { + return Enumerable.Empty<BoxSet>(); + } + + return _linkedChildrenService + .GetManualLinkedParentIds(itemId, BaseItemKind.BoxSet) + .Select(parentId => _libraryManager.GetItemById<BoxSet>(parentId, user)) + .OfType<BoxSet>(); + } + private IEnumerable<BoxSet> GetCollections(User user) { var folder = GetCollectionsFolder(false).GetAwaiter().GetResult(); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index cc85f09d23..a826db090f 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -3395,9 +3395,9 @@ namespace Emby.Server.Implementations.Library } /// <inheritdoc/> - public IReadOnlyList<string> GetPeopleNamesByItems(IReadOnlyList<Guid> itemIds, IReadOnlyList<string> personTypes, int limit) + public IReadOnlyDictionary<Guid, IReadOnlyList<string>> GetPeopleNamesByItems(IReadOnlyList<Guid> itemIds, IReadOnlyList<string> personTypes) { - return _peopleRepository.GetPeopleNamesByItems(itemIds, personTypes, limit); + return _peopleRepository.GetPeopleNamesByItems(itemIds, personTypes); } public void UpdatePeople(BaseItem item, List<PersonInfo> people) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 66614c6725..9ccfefa86e 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -24,6 +24,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -127,6 +128,11 @@ namespace Emby.Server.Implementations.Library return true; } + if (stream.IsVobSubSubtitleStream) + { + return true; + } + return false; } @@ -171,6 +177,7 @@ namespace Emby.Server.Implementations.Library public async Task<IReadOnlyList<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) { var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); + ResolveSymlinkPaths(mediaSources, enablePathSubstitution); // If file is strm or main media stream is missing, force a metadata refresh with remote probing if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder @@ -187,6 +194,7 @@ namespace Emby.Server.Implementations.Library cancellationToken).ConfigureAwait(false); mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); + ResolveSymlinkPaths(mediaSources, enablePathSubstitution); } var dynamicMediaSources = await GetDynamicMediaSources(item, cancellationToken).ConfigureAwait(false); @@ -319,6 +327,28 @@ namespace Emby.Server.Implementations.Library } } + /// <summary> + /// Resolves symlinked file paths on the supplied sources to the real on-disk target. + /// Skipped when <paramref name="enablePathSubstitution"/> is set because the path may + /// already have been rewritten to a UNC/URL meant for the client to consume directly. + /// </summary> + private static void ResolveSymlinkPaths(IReadOnlyList<MediaSourceInfo> sources, bool enablePathSubstitution) + { + if (enablePathSubstitution) + { + return; + } + + foreach (var source in sources) + { + if (source.Protocol == MediaProtocol.File + && FileSystemHelper.ResolveLinkTarget(source.Path, returnFinalTarget: true) is { Exists: true } target) + { + source.Path = target.FullName; + } + } + } + private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource) { var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamIdDelimiter; diff --git a/Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs b/Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs index 358c170db2..d923cff07e 100644 --- a/Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs +++ b/Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs @@ -125,6 +125,7 @@ public class SimilarItemsManager : ISimilarItemsManager var allResults = new List<(BaseItem Item, float Score)>(); var excludeIds = new HashSet<Guid> { item.Id }; + var excludeKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { item.GetPresentationUniqueKey() }; foreach (var (providerOrder, provider) in orderedProviders.Index()) { if (allResults.Count >= requestedLimit || cancellationToken.IsCancellationRequested) @@ -149,7 +150,9 @@ public class SimilarItemsManager : ISimilarItemsManager foreach (var (position, resultItem) in items.Index()) { - if (excludeIds.Add(resultItem.Id)) + var isNewId = excludeIds.Add(resultItem.Id); + var isNewKey = excludeKeys.Add(resultItem.GetPresentationUniqueKey()); + if (isNewId && isNewKey) { var score = CalculateScore(null, providerOrder, position); allResults.Add((resultItem, score)); @@ -163,7 +166,7 @@ public class SimilarItemsManager : ISimilarItemsManager var cachedReferences = await TryReadSimilarItemsCacheAsync(cachePath, cancellationToken).ConfigureAwait(false); if (cachedReferences is not null) { - var resolvedItems = ResolveRemoteReferences(cachedReferences, providerOrder, user, dtoOptions, itemKind, excludeIds); + var resolvedItems = ResolveRemoteReferences(cachedReferences, providerOrder, user, dtoOptions, itemKind, excludeIds, excludeKeys); allResults.AddRange(resolvedItems); continue; } @@ -191,7 +194,7 @@ public class SimilarItemsManager : ISimilarItemsManager if (pendingBatch.Count >= BatchSize) { - var resolvedItems = ResolveRemoteReferences(pendingBatch, providerOrder, user, dtoOptions, itemKind, excludeIds); + var resolvedItems = ResolveRemoteReferences(pendingBatch, providerOrder, user, dtoOptions, itemKind, excludeIds, excludeKeys); allResults.AddRange(resolvedItems); remaining -= resolvedItems.Count; pendingBatch.Clear(); @@ -206,7 +209,7 @@ public class SimilarItemsManager : ISimilarItemsManager // Resolve any remaining references in the last partial batch if (pendingBatch.Count > 0) { - var resolvedItems = ResolveRemoteReferences(pendingBatch, providerOrder, user, dtoOptions, itemKind, excludeIds); + var resolvedItems = ResolveRemoteReferences(pendingBatch, providerOrder, user, dtoOptions, itemKind, excludeIds, excludeKeys); allResults.AddRange(resolvedItems); } @@ -435,7 +438,11 @@ public class SimilarItemsManager : ISimilarItemsManager private IReadOnlyList<string> GetPeopleNames(IReadOnlyList<BaseItem> items, IReadOnlyList<string> personTypes) { var itemIds = items.Select(i => i.Id).ToArray(); - return _libraryManager.GetPeopleNamesByItems(itemIds, personTypes, limit: 0); + return _libraryManager.GetPeopleNamesByItems(itemIds, personTypes) + .Values + .SelectMany(names => names) + .Distinct() + .ToArray(); } private List<(BaseItem Item, float Score)> ResolveRemoteReferences( @@ -444,14 +451,15 @@ public class SimilarItemsManager : ISimilarItemsManager User? user, DtoOptions dtoOptions, BaseItemKind itemKind, - HashSet<Guid> excludeIds) + HashSet<Guid> excludeIds, + HashSet<string> excludeKeys) { if (references.Count == 0) { return []; } - var resolvedById = new Dictionary<Guid, (BaseItem Item, float Score)>(); + var resolvedByKey = new Dictionary<string, (BaseItem Item, float Score)>(StringComparer.OrdinalIgnoreCase); var providerLookup = new Dictionary<(string ProviderName, string ProviderId), (float? Score, int Position)>(StringTupleComparer.Instance); foreach (var (position, match) in references.Index()) @@ -482,7 +490,13 @@ public class SimilarItemsManager : ISimilarItemsManager foreach (var item in items) { - if (excludeIds.Contains(item.Id) || resolvedById.ContainsKey(item.Id)) + if (excludeIds.Contains(item.Id)) + { + continue; + } + + var presentationKey = item.GetPresentationUniqueKey(); + if (excludeKeys.Contains(presentationKey)) { continue; } @@ -492,10 +506,9 @@ public class SimilarItemsManager : ISimilarItemsManager if (item.TryGetProviderId(providerName, out var itemProviderId) && providerLookup.TryGetValue((providerName, itemProviderId), out var matchInfo)) { var score = CalculateScore(matchInfo.Score, providerOrder, matchInfo.Position); - if (!resolvedById.TryGetValue(item.Id, out var existing) || existing.Score < score) + if (!resolvedByKey.TryGetValue(presentationKey, out var existing) || existing.Score < score) { - excludeIds.Add(item.Id); - resolvedById[item.Id] = (item, score); + resolvedByKey[presentationKey] = (item, score); } break; @@ -503,7 +516,13 @@ public class SimilarItemsManager : ISimilarItemsManager } } - return [.. resolvedById.Values]; + foreach (var (key, entry) in resolvedByKey) + { + excludeIds.Add(entry.Item.Id); + excludeKeys.Add(key); + } + + return [.. resolvedByKey.Values]; } private static float CalculateScore(float? matchScore, int providerOrder, int position) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 5148b62655..18811ef3a9 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -453,18 +453,6 @@ namespace Emby.Server.Implementations.Session session.PlayState.RepeatMode = info.RepeatMode; session.PlayState.PlaybackOrder = info.PlaybackOrder; session.PlaylistItemId = info.PlaylistItemId; - - var nowPlayingQueue = info.NowPlayingQueue; - - if (nowPlayingQueue?.Length > 0 && !nowPlayingQueue.SequenceEqual(session.NowPlayingQueue)) - { - session.NowPlayingQueue = nowPlayingQueue; - - var itemIds = Array.ConvertAll(nowPlayingQueue, queue => queue.Id); - session.NowPlayingQueueFullItems = _dtoService.GetBaseItemDtos( - _libraryManager.GetItemList(new InternalItemsQuery { ItemIds = itemIds }), - new DtoOptions(true)); - } } /// <summary> @@ -1217,7 +1205,6 @@ namespace Emby.Server.Implementations.Session SupportsMediaControl = sessionInfo.SupportsMediaControl, SupportsRemoteControl = sessionInfo.SupportsRemoteControl, NowPlayingQueue = sessionInfo.NowPlayingQueue, - NowPlayingQueueFullItems = sessionInfo.NowPlayingQueueFullItems, HasCustomDeviceName = sessionInfo.HasCustomDeviceName, PlaylistItemId = sessionInfo.PlaylistItemId, ServerId = sessionInfo.ServerId, diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 67b77a112d..ef53e3b326 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -527,42 +527,44 @@ namespace Emby.Server.Implementations.Updates using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(new Uri(package.SourceUrl), cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - - // CA5351: Do Not Use Broken Cryptographic Algorithms + Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using (stream.ConfigureAwait(false)) + { + // CA5351: Do Not Use Broken Cryptographic Algorithms #pragma warning disable CA5351 - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - var hash = Convert.ToHexString(await MD5.HashDataAsync(stream, cancellationToken).ConfigureAwait(false)); - if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase)) - { - _logger.LogError( - "The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}", - package.Name, - package.Checksum, - hash); - throw new InvalidDataException("The checksum of the received data doesn't match."); - } + var hash = Convert.ToHexString(await MD5.HashDataAsync(stream, cancellationToken).ConfigureAwait(false)); + if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogError( + "The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}", + package.Name, + package.Checksum, + hash); + throw new InvalidDataException("The checksum of the received data doesn't match."); + } - // Version folder as they cannot be overwritten in Windows. - targetDir += "_" + package.Version; + // Version folder as they cannot be overwritten in Windows. + targetDir += "_" + package.Version; - if (Directory.Exists(targetDir)) - { - try + if (Directory.Exists(targetDir)) { - Directory.Delete(targetDir, true); - } + try + { + Directory.Delete(targetDir, true); + } #pragma warning disable CA1031 // Do not catch general exception types - catch + catch #pragma warning restore CA1031 // Do not catch general exception types - { - // Ignore any exceptions. + { + // Ignore any exceptions. + } } - } - stream.Position = 0; - await ZipFile.ExtractToDirectoryAsync(stream, targetDir, true, cancellationToken); + stream.Position = 0; + await ZipFile.ExtractToDirectoryAsync(stream, targetDir, true, cancellationToken).ConfigureAwait(false); + } // Ensure we create one or populate existing ones with missing data. await _pluginManager.PopulateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false); |
