AggregateFolder.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. #nullable disable
  2. #pragma warning disable CA1819, CS1591
  3. using System;
  4. using System.Collections.Concurrent;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Text.Json.Serialization;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using Jellyfin.Extensions;
  11. using MediaBrowser.Controller.IO;
  12. using MediaBrowser.Controller.Library;
  13. using MediaBrowser.Controller.Providers;
  14. using MediaBrowser.Model.IO;
  15. namespace MediaBrowser.Controller.Entities
  16. {
  17. /// <summary>
  18. /// Specialized folder that can have items added to it's children by external entities.
  19. /// Used for our RootFolder so plugins can add items.
  20. /// </summary>
  21. public class AggregateFolder : Folder
  22. {
  23. private readonly object _childIdsLock = new object();
  24. /// <summary>
  25. /// The _virtual children.
  26. /// </summary>
  27. private readonly ConcurrentBag<BaseItem> _virtualChildren = new ConcurrentBag<BaseItem>();
  28. private bool _requiresRefresh;
  29. private Guid[] _childrenIds = null;
  30. public AggregateFolder()
  31. {
  32. PhysicalLocationsList = Array.Empty<string>();
  33. }
  34. /// <summary>
  35. /// Gets the virtual children.
  36. /// </summary>
  37. /// <value>The virtual children.</value>
  38. public ConcurrentBag<BaseItem> VirtualChildren => _virtualChildren;
  39. [JsonIgnore]
  40. public override bool IsPhysicalRoot => true;
  41. [JsonIgnore]
  42. public override bool SupportsPlayedStatus => false;
  43. [JsonIgnore]
  44. public override string[] PhysicalLocations => PhysicalLocationsList;
  45. public string[] PhysicalLocationsList { get; set; }
  46. public override bool CanDelete()
  47. {
  48. return false;
  49. }
  50. protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
  51. {
  52. return CreateResolveArgs(directoryService, true).FileSystemChildren;
  53. }
  54. protected override List<BaseItem> LoadChildren()
  55. {
  56. lock (_childIdsLock)
  57. {
  58. if (_childrenIds is null || _childrenIds.Length == 0)
  59. {
  60. var list = base.LoadChildren();
  61. _childrenIds = list.Select(i => i.Id).ToArray();
  62. return list;
  63. }
  64. return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i is not null).ToList();
  65. }
  66. }
  67. private void ClearCache()
  68. {
  69. lock (_childIdsLock)
  70. {
  71. _childrenIds = null;
  72. }
  73. }
  74. public override bool RequiresRefresh()
  75. {
  76. var changed = base.RequiresRefresh() || _requiresRefresh;
  77. if (!changed)
  78. {
  79. var locations = PhysicalLocations;
  80. var newLocations = CreateResolveArgs(new DirectoryService(FileSystem), false).PhysicalLocations;
  81. if (!locations.SequenceEqual(newLocations))
  82. {
  83. changed = true;
  84. }
  85. }
  86. return changed;
  87. }
  88. public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
  89. {
  90. ClearCache();
  91. var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh;
  92. _requiresRefresh = false;
  93. return changed;
  94. }
  95. private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
  96. {
  97. ClearCache();
  98. var path = ContainingFolderPath;
  99. var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
  100. {
  101. FileInfo = FileSystem.GetDirectoryInfo(path)
  102. };
  103. // Gather child folder and files
  104. if (args.IsDirectory)
  105. {
  106. // When resolving the root, we need it's grandchildren (children of user views)
  107. var flattenFolderDepth = 2;
  108. var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, CollectionFolder.ApplicationHost, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: true);
  109. // Need to remove subpaths that may have been resolved from shortcuts
  110. // Example: if \\server\movies exists, then strip out \\server\movies\action
  111. files = LibraryManager.NormalizeRootPathList(files).ToArray();
  112. args.FileSystemChildren = files;
  113. }
  114. _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
  115. if (setPhysicalLocations)
  116. {
  117. PhysicalLocationsList = args.PhysicalLocations;
  118. }
  119. return args;
  120. }
  121. protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
  122. {
  123. return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren);
  124. }
  125. protected override async Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMetadata, bool allowRemoveRoot, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService, CancellationToken cancellationToken)
  126. {
  127. ClearCache();
  128. await base.ValidateChildrenInternal(progress, recursive, refreshChildMetadata, allowRemoveRoot, refreshOptions, directoryService, cancellationToken)
  129. .ConfigureAwait(false);
  130. ClearCache();
  131. }
  132. /// <summary>
  133. /// Adds the virtual child.
  134. /// </summary>
  135. /// <param name="child">The child.</param>
  136. /// <exception cref="ArgumentNullException">Throws if child is null.</exception>
  137. public void AddVirtualChild(BaseItem child)
  138. {
  139. ArgumentNullException.ThrowIfNull(child);
  140. _virtualChildren.Add(child);
  141. }
  142. /// <summary>
  143. /// Finds the virtual child.
  144. /// </summary>
  145. /// <param name="id">The id.</param>
  146. /// <returns>BaseItem.</returns>
  147. /// <exception cref="ArgumentNullException">The id is empty.</exception>
  148. public BaseItem FindVirtualChild(Guid id)
  149. {
  150. if (id.IsEmpty())
  151. {
  152. throw new ArgumentNullException(nameof(id));
  153. }
  154. foreach (var child in _virtualChildren)
  155. {
  156. if (child.Id.Equals(id))
  157. {
  158. return child;
  159. }
  160. }
  161. return null;
  162. }
  163. }
  164. }