< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Entities.CollectionFolder
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Entities/CollectionFolder.cs
Line coverage
65%
Covered lines: 78
Uncovered lines: 42
Coverable lines: 120
Total lines: 373
Line coverage: 65%
Branch coverage
45%
Covered branches: 21
Total branches: 46
Branch coverage: 45.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

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.Entities;
 15using Jellyfin.Data.Enums;
 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    {
 134        private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 135        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>
 1341        public CollectionFolder()
 42        {
 1343            PhysicalLocationsList = Array.Empty<string>();
 1344            PhysicalFolderIds = Array.Empty<Guid>();
 1345        }
 46
 47        /// <summary>
 48        /// Gets the display preferences id.
 49        /// </summary>
 50        /// <remarks>
 51        /// Allow different display preferences for each collection folder.
 52        /// </remarks>
 53        /// <value>The display prefs id.</value>
 54        [JsonIgnore]
 055        public override Guid DisplayPreferencesId => Id;
 56
 57        [JsonIgnore]
 7358        public override string[] PhysicalLocations => PhysicalLocationsList;
 59
 60        public string[] PhysicalLocationsList { get; set; }
 61
 62        public Guid[] PhysicalFolderIds { get; set; }
 63
 64        public static IXmlSerializer XmlSerializer { get; set; }
 65
 66        public static IServerApplicationHost ApplicationHost { get; set; }
 67
 68        [JsonIgnore]
 069        public override bool SupportsPlayedStatus => false;
 70
 71        [JsonIgnore]
 072        public override bool SupportsInheritedParentImages => false;
 73
 74        public CollectionType? CollectionType { get; set; }
 75
 76        /// <summary>
 77        /// Gets the item's children.
 78        /// </summary>
 79        /// <remarks>
 80        /// Our children are actually just references to the ones in the physical root...
 81        /// </remarks>
 82        /// <value>The actual children.</value>
 83        [JsonIgnore]
 184        public override IEnumerable<BaseItem> Children => GetActualChildren();
 85
 86        [JsonIgnore]
 587        public override bool SupportsPeople => false;
 88
 89        public override bool CanDelete()
 90        {
 091            return false;
 92        }
 93
 94        public LibraryOptions GetLibraryOptions()
 95        {
 1896            return GetLibraryOptions(Path);
 97        }
 98
 99        public override bool IsVisible(User user)
 100        {
 0101            if (GetLibraryOptions().Enabled)
 102            {
 0103                return base.IsVisible(user);
 104            }
 105
 0106            return false;
 107        }
 108
 109        private static LibraryOptions LoadLibraryOptions(string path)
 110        {
 111            try
 112            {
 0113                if (XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) is not Librar
 114                {
 0115                    return new LibraryOptions();
 116                }
 117
 0118                foreach (var mediaPath in result.PathInfos)
 119                {
 0120                    if (!string.IsNullOrEmpty(mediaPath.Path))
 121                    {
 0122                        mediaPath.Path = ApplicationHost.ExpandVirtualPath(mediaPath.Path);
 123                    }
 124                }
 125
 0126                return result;
 127            }
 0128            catch (FileNotFoundException)
 129            {
 0130                return new LibraryOptions();
 131            }
 0132            catch (IOException)
 133            {
 0134                return new LibraryOptions();
 135            }
 0136            catch (Exception ex)
 137            {
 0138                Logger.LogError(ex, "Error loading library options");
 139
 0140                return new LibraryOptions();
 141            }
 0142        }
 143
 144        private static string GetLibraryOptionsPath(string path)
 145        {
 2146            return System.IO.Path.Combine(path, "options.xml");
 147        }
 148
 149        public void UpdateLibraryOptions(LibraryOptions options)
 150        {
 1151            SaveLibraryOptions(Path, options);
 1152        }
 153
 154        public static LibraryOptions GetLibraryOptions(string path)
 18155            => _libraryOptions.GetOrAdd(path, LoadLibraryOptions);
 156
 157        public static void SaveLibraryOptions(string path, LibraryOptions options)
 158        {
 2159            _libraryOptions[path] = options;
 160
 2161            var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOpt
 4162            foreach (var mediaPath in clone.PathInfos)
 163            {
 0164                if (!string.IsNullOrEmpty(mediaPath.Path))
 165                {
 0166                    mediaPath.Path = ApplicationHost.ReverseVirtualPath(mediaPath.Path);
 167                }
 168            }
 169
 2170            XmlSerializer.SerializeToFile(clone, GetLibraryOptionsPath(path));
 2171        }
 172
 173        public static void OnCollectionFolderChange()
 1174            => _libraryOptions.Clear();
 175
 176        public override bool IsSaveLocalMetadataEnabled()
 177        {
 8178            return true;
 179        }
 180
 181        protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
 182        {
 4183            return CreateResolveArgs(directoryService, true).FileSystemChildren;
 184        }
 185
 186        public override bool RequiresRefresh()
 187        {
 4188            var changed = base.RequiresRefresh() || _requiresRefresh;
 189
 4190            if (!changed)
 191            {
 4192                var locations = PhysicalLocations;
 193
 4194                var newLocations = CreateResolveArgs(new DirectoryService(FileSystem), false).PhysicalLocations;
 195
 4196                if (!locations.SequenceEqual(newLocations))
 197                {
 0198                    changed = true;
 199                }
 200            }
 201
 4202            if (!changed)
 203            {
 4204                var folderIds = PhysicalFolderIds;
 205
 4206                var newFolderIds = GetPhysicalFolders(false).Select(i => i.Id).ToList();
 207
 4208                if (!folderIds.SequenceEqual(newFolderIds))
 209                {
 0210                    changed = true;
 211                }
 212            }
 213
 4214            return changed;
 215        }
 216
 217        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
 218        {
 4219            var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh;
 4220            _requiresRefresh = false;
 4221            return changed;
 222        }
 223
 224        public override double? GetRefreshProgress()
 225        {
 1226            var folders = GetPhysicalFolders(true).ToList();
 1227            double totalProgresses = 0;
 1228            var foldersWithProgress = 0;
 229
 2230            foreach (var folder in folders)
 231            {
 0232                var progress = ProviderManager.GetRefreshProgress(folder.Id);
 0233                if (progress.HasValue)
 234                {
 0235                    totalProgresses += progress.Value;
 0236                    foldersWithProgress++;
 237                }
 238            }
 239
 1240            if (foldersWithProgress == 0)
 241            {
 1242                return null;
 243            }
 244
 0245            return totalProgresses / foldersWithProgress;
 246        }
 247
 248        protected override bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
 249        {
 4250            return RefreshLinkedChildrenInternal(true);
 251        }
 252
 253        private bool RefreshLinkedChildrenInternal(bool setFolders)
 254        {
 4255            var physicalFolders = GetPhysicalFolders(false)
 4256                .ToList();
 257
 4258            var linkedChildren = physicalFolders
 4259                .SelectMany(c => c.LinkedChildren)
 4260                .ToList();
 261
 4262            var changed = !linkedChildren.SequenceEqual(LinkedChildren, new LinkedChildComparer(FileSystem));
 263
 4264            LinkedChildren = linkedChildren.ToArray();
 265
 4266            var folderIds = PhysicalFolderIds;
 4267            var newFolderIds = physicalFolders.Select(i => i.Id).ToArray();
 268
 4269            if (!folderIds.SequenceEqual(newFolderIds))
 270            {
 0271                changed = true;
 0272                if (setFolders)
 273                {
 0274                    PhysicalFolderIds = newFolderIds;
 275                }
 276            }
 277
 4278            return changed;
 279        }
 280
 281        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
 282        {
 8283            var path = ContainingFolderPath;
 284
 8285            var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
 8286            {
 8287                FileInfo = FileSystem.GetDirectoryInfo(path),
 8288                Parent = GetParent() as Folder,
 8289                CollectionType = CollectionType
 8290            };
 291
 292            // Gather child folder and files
 8293            if (args.IsDirectory)
 294            {
 8295                var flattenFolderDepth = 0;
 296
 8297                var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, ApplicationHo
 298
 8299                args.FileSystemChildren = files;
 300            }
 301
 8302            _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
 303
 8304            if (setPhysicalLocations)
 305            {
 4306                PhysicalLocationsList = args.PhysicalLocations;
 307            }
 308
 8309            return args;
 310        }
 311
 312        /// <summary>
 313        /// Compare our current children (presumably just read from the repo) with the current state of the file system 
 314        /// ***Currently does not contain logic to maintain items that are unavailable in the file system***.
 315        /// </summary>
 316        /// <param name="progress">The progress.</param>
 317        /// <param name="recursive">if set to <c>true</c> [recursive].</param>
 318        /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
 319        /// <param name="allowRemoveRoot">remove item even this folder is root.</param>
 320        /// <param name="refreshOptions">The refresh options.</param>
 321        /// <param name="directoryService">The directory service.</param>
 322        /// <param name="cancellationToken">The cancellation token.</param>
 323        /// <returns>Task.</returns>
 324        protected override Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMe
 325        {
 0326            return Task.CompletedTask;
 327        }
 328
 329        public IEnumerable<BaseItem> GetActualChildren()
 330        {
 1331            return GetPhysicalFolders(true).SelectMany(c => c.Children);
 332        }
 333
 334        public IEnumerable<Folder> GetPhysicalFolders()
 335        {
 0336            return GetPhysicalFolders(true);
 337        }
 338
 339        private IEnumerable<Folder> GetPhysicalFolders(bool enableCache)
 340        {
 10341            if (enableCache)
 342            {
 2343                return PhysicalFolderIds.Select(i => LibraryManager.GetItemById(i)).OfType<Folder>();
 344            }
 345
 8346            var rootChildren = LibraryManager.RootFolder.Children
 8347                .OfType<Folder>()
 8348                .ToList();
 349
 8350            return PhysicalLocations
 8351                    .Where(i => !FileSystem.AreEqual(i, Path))
 8352                    .SelectMany(i => GetPhysicalParents(i, rootChildren))
 8353                    .DistinctBy(x => x.Id);
 354        }
 355
 356        private IEnumerable<Folder> GetPhysicalParents(string path, List<Folder> rootChildren)
 357        {
 0358            var result = rootChildren
 0359                .Where(i => FileSystem.AreEqual(i.Path, path))
 0360                .ToList();
 361
 0362            if (result.Count == 0)
 363            {
 0364                if (LibraryManager.FindByPath(path, true) is Folder folder)
 365                {
 0366                    result.Add(folder);
 367                }
 368            }
 369
 0370            return result;
 371        }
 372    }
 373}