< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Collections.CollectionManager
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Collections/CollectionManager.cs
Line coverage
37%
Covered lines: 15
Uncovered lines: 25
Coverable lines: 40
Total lines: 367
Line coverage: 37.5%
Branch coverage
0%
Covered branches: 0
Total branches: 20
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
FindFolders(...)100%11100%
GetCollectionsFolderPath()100%11100%
GetCollectionsFolder(...)100%11100%
GetCollections(...)0%620%
AddToCollectionAsync(...)100%210%
CollapseItemsWithinBoxSets(...)0%342180%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Collections/CollectionManager.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Jellyfin.Data.Entities;
 8using MediaBrowser.Common.Configuration;
 9using MediaBrowser.Controller.Collections;
 10using MediaBrowser.Controller.Entities;
 11using MediaBrowser.Controller.Entities.Movies;
 12using MediaBrowser.Controller.Library;
 13using MediaBrowser.Controller.Providers;
 14using MediaBrowser.Model.Configuration;
 15using MediaBrowser.Model.Entities;
 16using MediaBrowser.Model.Globalization;
 17using MediaBrowser.Model.IO;
 18using Microsoft.Extensions.Logging;
 19
 20namespace Emby.Server.Implementations.Collections
 21{
 22    /// <summary>
 23    /// The collection manager.
 24    /// </summary>
 25    public class CollectionManager : ICollectionManager
 26    {
 27        private readonly ILibraryManager _libraryManager;
 28        private readonly IFileSystem _fileSystem;
 29        private readonly ILibraryMonitor _iLibraryMonitor;
 30        private readonly ILogger<CollectionManager> _logger;
 31        private readonly IProviderManager _providerManager;
 32        private readonly ILocalizationManager _localizationManager;
 33        private readonly IApplicationPaths _appPaths;
 34
 35        /// <summary>
 36        /// Initializes a new instance of the <see cref="CollectionManager"/> class.
 37        /// </summary>
 38        /// <param name="libraryManager">The library manager.</param>
 39        /// <param name="appPaths">The application paths.</param>
 40        /// <param name="localizationManager">The localization manager.</param>
 41        /// <param name="fileSystem">The filesystem.</param>
 42        /// <param name="iLibraryMonitor">The library monitor.</param>
 43        /// <param name="loggerFactory">The logger factory.</param>
 44        /// <param name="providerManager">The provider manager.</param>
 45        public CollectionManager(
 46            ILibraryManager libraryManager,
 47            IApplicationPaths appPaths,
 48            ILocalizationManager localizationManager,
 49            IFileSystem fileSystem,
 50            ILibraryMonitor iLibraryMonitor,
 51            ILoggerFactory loggerFactory,
 52            IProviderManager providerManager)
 53        {
 2254            _libraryManager = libraryManager;
 2255            _fileSystem = fileSystem;
 2256            _iLibraryMonitor = iLibraryMonitor;
 2257            _logger = loggerFactory.CreateLogger<CollectionManager>();
 2258            _providerManager = providerManager;
 2259            _localizationManager = localizationManager;
 2260            _appPaths = appPaths;
 2261        }
 62
 63        /// <inheritdoc />
 64        public event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
 65
 66        /// <inheritdoc />
 67        public event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
 68
 69        /// <inheritdoc />
 70        public event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
 71
 72        private IEnumerable<Folder> FindFolders(string path)
 73        {
 174            return _libraryManager
 175                .RootFolder
 176                .Children
 177                .OfType<Folder>()
 178                .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path));
 79        }
 80
 81        internal async Task<Folder?> EnsureLibraryFolder(string path, bool createIfNeeded)
 82        {
 83            var existingFolder = FindFolders(path).FirstOrDefault();
 84            if (existingFolder is not null)
 85            {
 86                return existingFolder;
 87            }
 88
 89            if (!createIfNeeded)
 90            {
 91                return null;
 92            }
 93
 94            Directory.CreateDirectory(path);
 95
 96            var libraryOptions = new LibraryOptions
 97            {
 98                PathInfos = new[] { new MediaPathInfo(path) },
 99                EnableRealtimeMonitor = false,
 100                SaveLocalMetadata = true
 101            };
 102
 103            var name = _localizationManager.GetLocalizedString("Collections");
 104
 105            await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.boxsets, libraryOptions, true).ConfigureA
 106
 107            return FindFolders(path).First();
 108        }
 109
 110        internal string GetCollectionsFolderPath()
 111        {
 1112            return Path.Combine(_appPaths.DataPath, "collections");
 113        }
 114
 115        /// <inheritdoc />
 116        public Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
 117        {
 1118            return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
 119        }
 120
 121        private IEnumerable<BoxSet> GetCollections(User user)
 122        {
 0123            var folder = GetCollectionsFolder(false).GetAwaiter().GetResult();
 124
 0125            return folder is null
 0126                ? Enumerable.Empty<BoxSet>()
 0127                : folder.GetChildren(user, true).OfType<BoxSet>();
 128        }
 129
 130        /// <inheritdoc />
 131        public async Task<BoxSet> CreateCollectionAsync(CollectionCreationOptions options)
 132        {
 133            var name = options.Name;
 134
 135            // Need to use the [boxset] suffix
 136            // If internet metadata is not found, or if xml saving is off there will be no collection.xml
 137            // This could cause it to get re-resolved as a plain folder
 138            var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
 139
 140            var parentFolder = await GetCollectionsFolder(true).ConfigureAwait(false);
 141
 142            if (parentFolder is null)
 143            {
 144                throw new ArgumentException(nameof(parentFolder));
 145            }
 146
 147            var path = Path.Combine(parentFolder.Path, folderName);
 148
 149            _iLibraryMonitor.ReportFileSystemChangeBeginning(path);
 150
 151            try
 152            {
 153                Directory.CreateDirectory(path);
 154
 155                var collection = new BoxSet
 156                {
 157                    Name = name,
 158                    Path = path,
 159                    IsLocked = options.IsLocked,
 160                    ProviderIds = options.ProviderIds,
 161                    DateCreated = DateTime.UtcNow
 162                };
 163
 164                parentFolder.AddChild(collection);
 165
 166                if (options.ItemIdList.Count > 0)
 167                {
 168                    await AddToCollectionAsync(
 169                        collection.Id,
 170                        options.ItemIdList.Select(x => new Guid(x)),
 171                        false,
 172                        new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 173                        {
 174                            // The initial adding of items is going to create a local metadata file
 175                            // This will cause internet metadata to be skipped as a result
 176                            MetadataRefreshMode = MetadataRefreshMode.FullRefresh
 177                        }).ConfigureAwait(false);
 178                }
 179                else
 180                {
 181                    _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSy
 182                }
 183
 184                CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs
 185                {
 186                    Collection = collection,
 187                    Options = options
 188                });
 189
 190                return collection;
 191            }
 192            finally
 193            {
 194                // Refresh handled internally
 195                _iLibraryMonitor.ReportFileSystemChangeComplete(path, false);
 196            }
 197        }
 198
 199        /// <inheritdoc />
 200        public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
 0201            => AddToCollectionAsync(collectionId, itemIds, true, new MetadataRefreshOptions(new DirectoryService(_fileSy
 202
 203        private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefres
 204        {
 205            if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
 206            {
 207                throw new ArgumentException("No collection exists with the supplied Id");
 208            }
 209
 210            List<BaseItem>? itemList = null;
 211
 212            var linkedChildrenList = collection.GetLinkedChildren();
 213            var currentLinkedChildrenIds = linkedChildrenList.Select(i => i.Id).ToList();
 214
 215            foreach (var id in ids)
 216            {
 217                var item = _libraryManager.GetItemById(id);
 218
 219                if (item is null)
 220                {
 221                    throw new ArgumentException("No item exists with the supplied Id");
 222                }
 223
 224                if (!currentLinkedChildrenIds.Contains(id))
 225                {
 226                    (itemList ??= new()).Add(item);
 227
 228                    linkedChildrenList.Add(item);
 229                }
 230            }
 231
 232            if (itemList is not null)
 233            {
 234                var originalLen = collection.LinkedChildren.Length;
 235                var newItemCount = itemList.Count;
 236                LinkedChild[] newChildren = new LinkedChild[originalLen + newItemCount];
 237                collection.LinkedChildren.CopyTo(newChildren, 0);
 238                for (int i = 0; i < newItemCount; i++)
 239                {
 240                    newChildren[originalLen + i] = LinkedChild.Create(itemList[i]);
 241                }
 242
 243                collection.LinkedChildren = newChildren;
 244                collection.UpdateRatingToItems(linkedChildrenList);
 245
 246                await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureA
 247
 248                refreshOptions.ForceSave = true;
 249                _providerManager.QueueRefresh(collection.Id, refreshOptions, RefreshPriority.High);
 250
 251                if (fireEvent)
 252                {
 253                    ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
 254                }
 255            }
 256        }
 257
 258        /// <inheritdoc />
 259        public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
 260        {
 261            if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
 262            {
 263                throw new ArgumentException("No collection exists with the supplied Id");
 264            }
 265
 266            var list = new List<LinkedChild>();
 267            var itemList = new List<BaseItem>();
 268
 269            foreach (var guidId in itemIds)
 270            {
 271                var childItem = _libraryManager.GetItemById(guidId);
 272
 273                var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value.Equals(gu
 274
 275                if (child is null)
 276                {
 277                    _logger.LogWarning("No collection title exists with the supplied Id");
 278                    continue;
 279                }
 280
 281                list.Add(child);
 282
 283                if (childItem is not null)
 284                {
 285                    itemList.Add(childItem);
 286                }
 287            }
 288
 289            if (list.Count > 0)
 290            {
 291                collection.LinkedChildren = collection.LinkedChildren.Except(list).ToArray();
 292            }
 293
 294            await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait
 295            _providerManager.QueueRefresh(
 296                collection.Id,
 297                new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 298                {
 299                    ForceSave = true
 300                },
 301                RefreshPriority.High);
 302
 303            ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
 304        }
 305
 306        /// <inheritdoc />
 307        public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
 308        {
 0309            var results = new Dictionary<Guid, BaseItem>();
 310
 0311            var allBoxSets = GetCollections(user).ToList();
 312
 0313            foreach (var item in items)
 314            {
 0315                if (item is ISupportsBoxSetGrouping)
 316                {
 0317                    var itemId = item.Id;
 318
 0319                    var itemIsInBoxSet = false;
 0320                    foreach (var boxSet in allBoxSets)
 321                    {
 0322                        if (!boxSet.ContainsLinkedChildByItemId(itemId))
 323                        {
 324                            continue;
 325                        }
 326
 0327                        itemIsInBoxSet = true;
 328
 0329                        results.TryAdd(boxSet.Id, boxSet);
 330                    }
 331
 332                    // skip any item that is in a box set
 0333                    if (itemIsInBoxSet)
 334                    {
 335                        continue;
 336                    }
 337
 0338                    var alreadyInResults = false;
 339
 340                    // this is kind of a performance hack because only Video has alternate versions that should be in a 
 0341                    if (item is Video video)
 342                    {
 0343                        foreach (var childId in video.GetLocalAlternateVersionIds())
 344                        {
 0345                            if (!results.ContainsKey(childId))
 346                            {
 347                                continue;
 348                            }
 349
 0350                            alreadyInResults = true;
 0351                            break;
 352                        }
 353                    }
 354
 0355                    if (alreadyInResults)
 356                    {
 357                        continue;
 358                    }
 359                }
 360
 0361                results[item.Id] = item;
 362            }
 363
 0364            return results.Values;
 365        }
 366    }
 367}