< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Entities.CollectionFolder
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Entities/CollectionFolder.cs
Line coverage
66%
Covered lines: 82
Uncovered lines: 42
Coverable lines: 124
Total lines: 393
Line coverage: 66.1%
Branch coverage
48%
Covered branches: 24
Total branches: 50
Branch coverage: 48%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3731/29/2026 - 12:13:32 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3732/6/2026 - 12:13:21 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3732/8/2026 - 12:14:11 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3733/3/2026 - 12:13:24 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3733/4/2026 - 12:14:01 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3734/1/2026 - 12:13:37 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3734/2/2026 - 12:14:03 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3734/28/2026 - 12:13:10 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3734/29/2026 - 12:14:58 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3735/4/2026 - 12:15:16 AM Line coverage: 66.1% (82/124) Branch coverage: 48% (24/50) Total lines: 393 1/23/2026 - 12:11:06 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3731/29/2026 - 12:13:32 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3732/6/2026 - 12:13:21 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3732/8/2026 - 12:14:11 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3733/3/2026 - 12:13:24 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3733/4/2026 - 12:14:01 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3734/1/2026 - 12:13:37 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3734/2/2026 - 12:14:03 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3734/28/2026 - 12:13:10 AM Line coverage: 69.1% (83/120) Branch coverage: 50% (23/46) Total lines: 3734/29/2026 - 12:14:58 AM Line coverage: 67.5% (81/120) Branch coverage: 50% (23/46) Total lines: 3735/4/2026 - 12:15:16 AM Line coverage: 66.1% (82/124) Branch coverage: 48% (24/50) Total lines: 393

Coverage delta

Coverage delta 2 -2

Metrics

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/Entities/CollectionFolder.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Concurrent;
 7using System.Collections.Generic;
 8using System.IO;
 9using System.Linq;
 10using System.Text.Json;
 11using System.Text.Json.Serialization;
 12using System.Threading;
 13using System.Threading.Tasks;
 14using Jellyfin.Data.Enums;
 15using Jellyfin.Database.Implementations.Entities;
 16using Jellyfin.Extensions.Json;
 17using MediaBrowser.Controller.IO;
 18using MediaBrowser.Controller.Library;
 19using MediaBrowser.Controller.Providers;
 20using MediaBrowser.Model.Configuration;
 21using MediaBrowser.Model.IO;
 22using MediaBrowser.Model.Serialization;
 23
 24using Microsoft.Extensions.Logging;
 25
 26namespace MediaBrowser.Controller.Entities
 27{
 28    /// <summary>
 29    /// Specialized Folder class that points to a subset of the physical folders in the system.
 30    /// It is created from the user-specific folders within the system root.
 31    /// </summary>
 32    public class CollectionFolder : Folder, ICollectionFolder
 33    {
 234        private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 235        private static readonly ConcurrentDictionary<string, LibraryOptions> _libraryOptions = new ConcurrentDictionary<
 36        private bool _requiresRefresh;
 37
 38        /// <summary>
 39        /// Initializes a new instance of the <see cref="CollectionFolder"/> class.
 40        /// </summary>
 2341        public CollectionFolder()
 42        {
 2343            PhysicalLocationsList = Array.Empty<string>();
 2344            PhysicalFolderIds = Array.Empty<Guid>();
 2345        }
 46
 47        /// <summary>
 48        /// Event raised when library options are updated for any collection folder.
 49        /// </summary>
 50        public static event EventHandler<LibraryOptionsUpdatedEventArgs> LibraryOptionsUpdated;
 51
 52        /// <summary>
 53        /// Gets the display preferences id.
 54        /// </summary>
 55        /// <remarks>
 56        /// Allow different display preferences for each collection folder.
 57        /// </remarks>
 58        /// <value>The display prefs id.</value>
 59        [JsonIgnore]
 060        public override Guid DisplayPreferencesId => Id;
 61
 62        [JsonIgnore]
 19263        public override string[] PhysicalLocations => PhysicalLocationsList;
 64
 65        public string[] PhysicalLocationsList { get; set; }
 66
 67        public Guid[] PhysicalFolderIds { get; set; }
 68
 69        public static IXmlSerializer XmlSerializer { get; set; }
 70
 71        public static IServerApplicationHost ApplicationHost { get; set; }
 72
 73        [JsonIgnore]
 074        public override bool SupportsPlayedStatus => false;
 75
 76        [JsonIgnore]
 077        public override bool SupportsInheritedParentImages => false;
 78
 79        public CollectionType? CollectionType { get; set; }
 80
 81        /// <summary>
 82        /// Gets or sets the item's children.
 83        /// </summary>
 84        /// <remarks>
 85        /// Our children are actually just references to the ones in the physical root...
 86        /// Setting to null propagates invalidation to physical folders since the getter
 87        /// always delegates to <see cref="GetActualChildren"/> and never reads the backing field.
 88        /// </remarks>
 89        /// <value>The actual children.</value>
 90        [JsonIgnore]
 91        public override IEnumerable<BaseItem> Children
 92        {
 093            get => GetActualChildren();
 94            set
 95            {
 96                // The getter delegates to physical folders, so invalidate their caches.
 097                foreach (var folder in GetPhysicalFolders(true))
 98                {
 099                    folder.Children = null;
 100                }
 0101            }
 102        }
 103
 104        [JsonIgnore]
 4105        public override bool SupportsPeople => false;
 106
 107        public override bool CanDelete()
 108        {
 1109            return false;
 110        }
 111
 112        public LibraryOptions GetLibraryOptions()
 113        {
 38114            return GetLibraryOptions(Path);
 115        }
 116
 117        public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
 118        {
 0119            if (GetLibraryOptions().Enabled)
 120            {
 0121                return base.IsVisible(user, skipAllowedTagsCheck);
 122            }
 123
 0124            return false;
 125        }
 126
 127        private static LibraryOptions LoadLibraryOptions(string path)
 128        {
 129            try
 130            {
 2131                if (XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) is not Librar
 132                {
 0133                    return new LibraryOptions();
 134                }
 135
 4136                foreach (var mediaPath in result.PathInfos)
 137                {
 0138                    if (!string.IsNullOrEmpty(mediaPath.Path))
 139                    {
 0140                        mediaPath.Path = ApplicationHost.ExpandVirtualPath(mediaPath.Path);
 141                    }
 142                }
 143
 2144                return result;
 145            }
 0146            catch (FileNotFoundException)
 147            {
 0148                return new LibraryOptions();
 149            }
 0150            catch (IOException)
 151            {
 0152                return new LibraryOptions();
 153            }
 0154            catch (Exception ex)
 155            {
 0156                Logger.LogError(ex, "Error loading library options");
 157
 0158                return new LibraryOptions();
 159            }
 2160        }
 161
 162        private static string GetLibraryOptionsPath(string path)
 163        {
 5164            return System.IO.Path.Combine(path, "options.xml");
 165        }
 166
 167        public void UpdateLibraryOptions(LibraryOptions options)
 168        {
 1169            SaveLibraryOptions(Path, options);
 1170        }
 171
 172        public static LibraryOptions GetLibraryOptions(string path)
 38173            => _libraryOptions.GetOrAdd(path, LoadLibraryOptions);
 174
 175        public static void SaveLibraryOptions(string path, LibraryOptions options)
 176        {
 3177            _libraryOptions[path] = options;
 178
 3179            var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOpt
 6180            foreach (var mediaPath in clone.PathInfos)
 181            {
 0182                if (!string.IsNullOrEmpty(mediaPath.Path))
 183                {
 0184                    mediaPath.Path = ApplicationHost.ReverseVirtualPath(mediaPath.Path);
 185                }
 186            }
 187
 3188            XmlSerializer.SerializeToFile(clone, GetLibraryOptionsPath(path));
 189
 3190            LibraryOptionsUpdated?.Invoke(null, new LibraryOptionsUpdatedEventArgs(path, options));
 3191        }
 192
 193        public static void OnCollectionFolderChange()
 1194            => _libraryOptions.Clear();
 195
 196        public override bool IsSaveLocalMetadataEnabled()
 197        {
 28198            return true;
 199        }
 200
 201        protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
 202        {
 14203            return CreateResolveArgs(directoryService, true).FileSystemChildren;
 204        }
 205
 206        public override bool RequiresRefresh()
 207        {
 14208            var changed = base.RequiresRefresh() || _requiresRefresh;
 209
 14210            if (!changed)
 211            {
 14212                var locations = PhysicalLocations;
 213
 14214                var newLocations = CreateResolveArgs(new DirectoryService(FileSystem), false).PhysicalLocations;
 215
 14216                if (!locations.SequenceEqual(newLocations))
 217                {
 0218                    changed = true;
 219                }
 220            }
 221
 14222            if (!changed)
 223            {
 14224                var folderIds = PhysicalFolderIds;
 225
 14226                var newFolderIds = GetPhysicalFolders(false).Select(i => i.Id).ToList();
 227
 14228                if (!folderIds.SequenceEqual(newFolderIds))
 229                {
 0230                    changed = true;
 231                }
 232            }
 233
 14234            return changed;
 235        }
 236
 237        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
 238        {
 2239            var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh;
 2240            _requiresRefresh = false;
 2241            return changed;
 242        }
 243
 244        public override double? GetRefreshProgress()
 245        {
 1246            var folders = GetPhysicalFolders(true).ToList();
 1247            double totalProgresses = 0;
 1248            var foldersWithProgress = 0;
 249
 2250            foreach (var folder in folders)
 251            {
 0252                var progress = ProviderManager.GetRefreshProgress(folder.Id);
 0253                if (progress.HasValue)
 254                {
 0255                    totalProgresses += progress.Value;
 0256                    foldersWithProgress++;
 257                }
 258            }
 259
 1260            if (foldersWithProgress == 0)
 261            {
 1262                return null;
 263            }
 264
 0265            return totalProgresses / foldersWithProgress;
 266        }
 267
 268        protected override bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
 269        {
 14270            return RefreshLinkedChildrenInternal(true);
 271        }
 272
 273        private bool RefreshLinkedChildrenInternal(bool setFolders)
 274        {
 14275            var physicalFolders = GetPhysicalFolders(false)
 14276                .ToList();
 277
 14278            var linkedChildren = physicalFolders
 14279                .SelectMany(c => c.LinkedChildren)
 14280                .ToList();
 281
 14282            var changed = !linkedChildren.SequenceEqual(LinkedChildren, new LinkedChildComparer(FileSystem));
 283
 14284            LinkedChildren = linkedChildren.ToArray();
 285
 14286            var folderIds = PhysicalFolderIds;
 14287            var newFolderIds = physicalFolders.Select(i => i.Id).ToArray();
 288
 14289            if (!folderIds.SequenceEqual(newFolderIds))
 290            {
 0291                changed = true;
 0292                if (setFolders)
 293                {
 0294                    PhysicalFolderIds = newFolderIds;
 295                }
 296            }
 297
 14298            return changed;
 299        }
 300
 301        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
 302        {
 28303            var path = ContainingFolderPath;
 304
 28305            var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
 28306            {
 28307                FileInfo = FileSystem.GetDirectoryInfo(path),
 28308                Parent = GetParent() as Folder,
 28309                CollectionType = CollectionType
 28310            };
 311
 312            // Gather child folder and files
 28313            if (args.IsDirectory)
 314            {
 28315                var flattenFolderDepth = 0;
 316
 28317                var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, ApplicationHo
 318
 28319                args.FileSystemChildren = files;
 320            }
 321
 28322            _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
 323
 28324            if (setPhysicalLocations)
 325            {
 14326                PhysicalLocationsList = args.PhysicalLocations;
 327            }
 328
 28329            return args;
 330        }
 331
 332        /// <summary>
 333        /// Compare our current children (presumably just read from the repo) with the current state of the file system 
 334        /// ***Currently does not contain logic to maintain items that are unavailable in the file system***.
 335        /// </summary>
 336        /// <param name="progress">The progress.</param>
 337        /// <param name="recursive">if set to <c>true</c> [recursive].</param>
 338        /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
 339        /// <param name="allowRemoveRoot">remove item even this folder is root.</param>
 340        /// <param name="refreshOptions">The refresh options.</param>
 341        /// <param name="directoryService">The directory service.</param>
 342        /// <param name="cancellationToken">The cancellation token.</param>
 343        /// <returns>Task.</returns>
 344        protected override Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMe
 345        {
 0346            return Task.CompletedTask;
 347        }
 348
 349        public IEnumerable<BaseItem> GetActualChildren()
 350        {
 0351            return GetPhysicalFolders(true).SelectMany(c => c.Children);
 352        }
 353
 354        public IEnumerable<Folder> GetPhysicalFolders()
 355        {
 0356            return GetPhysicalFolders(true);
 357        }
 358
 359        private IEnumerable<Folder> GetPhysicalFolders(bool enableCache)
 360        {
 29361            if (enableCache)
 362            {
 1363                return PhysicalFolderIds.Select(i => LibraryManager.GetItemById(i)).OfType<Folder>();
 364            }
 365
 28366            var rootChildren = LibraryManager.RootFolder.Children
 28367                .OfType<Folder>()
 28368                .ToList();
 369
 28370            return PhysicalLocations
 28371                    .Where(i => !FileSystem.AreEqual(i, Path))
 28372                    .SelectMany(i => GetPhysicalParents(i, rootChildren))
 28373                    .DistinctBy(x => x.Id);
 374        }
 375
 376        private IEnumerable<Folder> GetPhysicalParents(string path, List<Folder> rootChildren)
 377        {
 0378            var result = rootChildren
 0379                .Where(i => FileSystem.AreEqual(i.Path, path))
 0380                .ToList();
 381
 0382            if (result.Count == 0)
 383            {
 0384                if (LibraryManager.FindByPath(path, true) is Folder folder)
 385                {
 0386                    result.Add(folder);
 387                }
 388            }
 389
 0390            return result;
 391        }
 392    }
 393}

Methods/Properties

.cctor()
.ctor()
get_DisplayPreferencesId()
get_PhysicalLocations()
get_SupportsPlayedStatus()
get_SupportsInheritedParentImages()
get_Children()
set_Children(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>)
get_SupportsPeople()
CanDelete()
GetLibraryOptions()
IsVisible(Jellyfin.Database.Implementations.Entities.User,System.Boolean)
LoadLibraryOptions(System.String)
GetLibraryOptionsPath(System.String)
UpdateLibraryOptions(MediaBrowser.Model.Configuration.LibraryOptions)
GetLibraryOptions(System.String)
SaveLibraryOptions(System.String,MediaBrowser.Model.Configuration.LibraryOptions)
OnCollectionFolderChange()
IsSaveLocalMetadataEnabled()
GetFileSystemChildren(MediaBrowser.Controller.Providers.IDirectoryService)
RequiresRefresh()
BeforeMetadataRefresh(System.Boolean)
GetRefreshProgress()
RefreshLinkedChildren(System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.IO.FileSystemMetadata>)
RefreshLinkedChildrenInternal(System.Boolean)
CreateResolveArgs(MediaBrowser.Controller.Providers.IDirectoryService,System.Boolean)
ValidateChildrenInternal(System.IProgress`1<System.Double>,System.Boolean,System.Boolean,System.Boolean,MediaBrowser.Controller.Providers.MetadataRefreshOptions,MediaBrowser.Controller.Providers.IDirectoryService,System.Threading.CancellationToken)
GetActualChildren()
GetPhysicalFolders()
GetPhysicalFolders(System.Boolean)
GetPhysicalParents(System.String,System.Collections.Generic.List`1<MediaBrowser.Controller.Entities.Folder>)