< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Entities.Folder
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Entities/Folder.cs
Line coverage
31%
Covered lines: 189
Uncovered lines: 402
Coverable lines: 591
Total lines: 1872
Line coverage: 31.9%
Branch coverage
26%
Covered branches: 104
Total branches: 398
Branch coverage: 26.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 8/15/2025 - 12:10:51 AM Line coverage: 33.1% (180/543) Branch coverage: 29.7% (107/360) Total lines: 17719/17/2025 - 12:11:23 AM Line coverage: 34.3% (194/565) Branch coverage: 29.9% (109/364) Total lines: 18019/21/2025 - 12:11:25 AM Line coverage: 34.3% (194/565) Branch coverage: 30% (110/366) Total lines: 18019/25/2025 - 12:11:18 AM Line coverage: 34.4% (195/566) Branch coverage: 29.7% (109/366) Total lines: 18029/26/2025 - 12:11:03 AM Line coverage: 34.2% (195/569) Branch coverage: 29.7% (109/366) Total lines: 180510/2/2025 - 12:11:36 AM Line coverage: 34.2% (195/569) Branch coverage: 29.7% (109/366) Total lines: 181010/3/2025 - 12:11:15 AM Line coverage: 34.2% (196/573) Branch coverage: 29.8% (111/372) Total lines: 181810/12/2025 - 12:11:07 AM Line coverage: 32.9% (189/573) Branch coverage: 28.7% (107/372) Total lines: 182410/28/2025 - 12:11:27 AM Line coverage: 32.9% (189/573) Branch coverage: 28.7% (107/372) Total lines: 182911/18/2025 - 12:11:25 AM Line coverage: 31.9% (189/591) Branch coverage: 26.1% (104/398) Total lines: 1872 8/15/2025 - 12:10:51 AM Line coverage: 33.1% (180/543) Branch coverage: 29.7% (107/360) Total lines: 17719/17/2025 - 12:11:23 AM Line coverage: 34.3% (194/565) Branch coverage: 29.9% (109/364) Total lines: 18019/21/2025 - 12:11:25 AM Line coverage: 34.3% (194/565) Branch coverage: 30% (110/366) Total lines: 18019/25/2025 - 12:11:18 AM Line coverage: 34.4% (195/566) Branch coverage: 29.7% (109/366) Total lines: 18029/26/2025 - 12:11:03 AM Line coverage: 34.2% (195/569) Branch coverage: 29.7% (109/366) Total lines: 180510/2/2025 - 12:11:36 AM Line coverage: 34.2% (195/569) Branch coverage: 29.7% (109/366) Total lines: 181010/3/2025 - 12:11:15 AM Line coverage: 34.2% (196/573) Branch coverage: 29.8% (111/372) Total lines: 181810/12/2025 - 12:11:07 AM Line coverage: 32.9% (189/573) Branch coverage: 28.7% (107/372) Total lines: 182410/28/2025 - 12:11:27 AM Line coverage: 32.9% (189/573) Branch coverage: 28.7% (107/372) Total lines: 182911/18/2025 - 12:11:25 AM Line coverage: 31.9% (189/591) Branch coverage: 26.1% (104/398) Total lines: 1872

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
get_SupportsThemeMedia()100%210%
get_IsPreSorted()100%210%
get_IsPhysicalRoot()100%11100%
get_SupportsInheritedParentImages()100%210%
get_SupportsPlayedStatus()100%210%
get_IsFolder()100%11100%
get_IsDisplayedAsFolder()100%210%
get_SupportsCumulativeRunTimeTicks()100%11100%
get_SupportsDateLastMediaAdded()100%11100%
get_FileNameWithoutExtension()50%2266.66%
get_Children()100%22100%
set_Children(...)100%11100%
get_RecursiveChildren()100%210%
get_SupportsShortcutChildren()100%11100%
get_FilterLinkedChildrenPerUser()100%11100%
get_SupportsOwnedItems()100%22100%
get_SupportsUserDataFromChildren()25%841635.71%
CanDelete()50%2266.66%
RequiresRefresh()50%4475%
AddChild(...)0%4260%
IsVisible(...)50%801222.22%
LoadChildren()100%11100%
GetRefreshProgress()100%210%
ValidateChildren(...)100%210%
ValidateChildren(...)100%11100%
GetActualChildrenDictionary()37.5%10866.66%
IsLibraryFolderAccessible(...)80%101083.33%
GetNonCachedChildren(...)100%11100%
GetCachedChildren()100%11100%
GetChildCount(...)0%2040%
GetRecursiveChildCount(...)100%210%
QueryRecursive(...)71.42%651436.36%
QueryWithPostFiltering2(...)62.5%9877.27%
RequiresPostFiltering2(...)50%6450%
RequiresPostFiltering(...)50%4203633.33%
SortItemsByRequest(...)100%210%
GetItems(...)0%4260%
GetItemList(...)16.66%13642.85%
GetItemsInternal(...)33.33%30612.5%
PostFilterAndSort(...)0%210140%
CollapseBoxSetItemsIfNeeded(...)0%506220%
CollapseBoxSetItems(...)25%1822435%
AllowBoxSetCollapsing(...)0%4692680%
GetChildren(...)75%4487.5%
GetChildren(...)100%11100%
GetEligibleChildrenForRecursiveChildren(...)100%11100%
AddChildren(...)71.42%171475%
AddChildrenFromCollection(...)50%282073.07%
GetRecursiveChildren(...)100%210%
GetRecursiveChildren()100%210%
GetRecursiveChildren(...)100%210%
GetRecursiveChildren(...)100%210%
GetRecursiveChildren(...)100%210%
AddChildrenToList(...)0%342180%
GetLinkedChildren()25%5457.14%
ContainsLinkedChildByItemId(...)0%110100%
GetLinkedChildren(...)9.09%406227.4%
GetLinkedChildrenInfos()100%210%
RefreshLinkedChildren(...)66.66%6687.8%
MarkPlayed(...)0%110100%
MarkUnplayed(...)0%620%
IsPlayed(...)100%210%
IsUnplayed(...)100%210%
FillUserDataDtoValues(...)8.33%124128%
GetProgress(...)100%11100%

File(s)

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

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CA1002, CA1721, CA1819, CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Collections.Immutable;
 8using System.IO;
 9using System.Linq;
 10using System.Security;
 11using System.Text.Json.Serialization;
 12using System.Threading;
 13using System.Threading.Tasks;
 14using J2N.Collections.Generic.Extensions;
 15using Jellyfin.Data;
 16using Jellyfin.Data.Enums;
 17using Jellyfin.Database.Implementations.Entities;
 18using Jellyfin.Database.Implementations.Enums;
 19using Jellyfin.Extensions;
 20using MediaBrowser.Controller.Channels;
 21using MediaBrowser.Controller.Collections;
 22using MediaBrowser.Controller.Configuration;
 23using MediaBrowser.Controller.Dto;
 24using MediaBrowser.Controller.Entities.Audio;
 25using MediaBrowser.Controller.Entities.Movies;
 26using MediaBrowser.Controller.Library;
 27using MediaBrowser.Controller.LibraryTaskScheduler;
 28using MediaBrowser.Controller.Providers;
 29using MediaBrowser.Model.Dto;
 30using MediaBrowser.Model.IO;
 31using MediaBrowser.Model.Querying;
 32using Microsoft.Extensions.Logging;
 33using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 34using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
 35using Season = MediaBrowser.Controller.Entities.TV.Season;
 36using Series = MediaBrowser.Controller.Entities.TV.Series;
 37
 38namespace MediaBrowser.Controller.Entities
 39{
 40    /// <summary>
 41    /// Class Folder.
 42    /// </summary>
 43    public class Folder : BaseItem
 44    {
 45        private IEnumerable<BaseItem> _children;
 46
 36847        public Folder()
 48        {
 36849            LinkedChildren = Array.Empty<LinkedChild>();
 36850        }
 51
 52        public static IUserViewManager UserViewManager { get; set; }
 53
 54        public static ILimitedConcurrencyLibraryScheduler LimitedConcurrencyLibraryScheduler { get; set; }
 55
 56        /// <summary>
 57        /// Gets or sets a value indicating whether this instance is root.
 58        /// </summary>
 59        /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
 60        public bool IsRoot { get; set; }
 61
 62        public LinkedChild[] LinkedChildren { get; set; }
 63
 64        [JsonIgnore]
 65        public DateTime? DateLastMediaAdded { get; set; }
 66
 67        [JsonIgnore]
 068        public override bool SupportsThemeMedia => true;
 69
 70        [JsonIgnore]
 071        public virtual bool IsPreSorted => false;
 72
 73        [JsonIgnore]
 1074        public virtual bool IsPhysicalRoot => false;
 75
 76        [JsonIgnore]
 077        public override bool SupportsInheritedParentImages => true;
 78
 79        [JsonIgnore]
 080        public override bool SupportsPlayedStatus => true;
 81
 82        /// <summary>
 83        /// Gets a value indicating whether this instance is folder.
 84        /// </summary>
 85        /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
 86        [JsonIgnore]
 77787        public override bool IsFolder => true;
 88
 89        [JsonIgnore]
 090        public override bool IsDisplayedAsFolder => true;
 91
 92        [JsonIgnore]
 9093        public virtual bool SupportsCumulativeRunTimeTicks => false;
 94
 95        [JsonIgnore]
 5496        public virtual bool SupportsDateLastMediaAdded => false;
 97
 98        [JsonIgnore]
 99        public override string FileNameWithoutExtension
 100        {
 101            get
 102            {
 289103                if (IsFileProtocol)
 104                {
 289105                    return System.IO.Path.GetFileName(Path);
 106                }
 107
 0108                return null;
 109            }
 110        }
 111
 112        /// <summary>
 113        /// Gets or Sets the actual children.
 114        /// </summary>
 115        /// <value>The actual children.</value>
 116        [JsonIgnore]
 117        public virtual IEnumerable<BaseItem> Children
 118        {
 987119            get => _children ??= LoadChildren();
 219120            set => _children = value;
 121        }
 122
 123        /// <summary>
 124        /// Gets thread-safe access to all recursive children of this folder - without regard to user.
 125        /// </summary>
 126        /// <value>The recursive children.</value>
 127        [JsonIgnore]
 0128        public IEnumerable<BaseItem> RecursiveChildren => GetRecursiveChildren();
 129
 130        [JsonIgnore]
 22131        protected virtual bool SupportsShortcutChildren => false;
 132
 10133        protected virtual bool FilterLinkedChildrenPerUser => false;
 134
 135        [JsonIgnore]
 88136        protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren;
 137
 138        [JsonIgnore]
 139        public virtual bool SupportsUserDataFromChildren
 140        {
 141            get
 142            {
 143                // These are just far too slow.
 9144                if (this is ICollectionFolder)
 145                {
 3146                    return false;
 147                }
 148
 6149                if (this is UserView)
 150                {
 0151                    return false;
 152                }
 153
 6154                if (this is UserRootFolder)
 155                {
 6156                    return false;
 157                }
 158
 0159                if (this is Channel)
 160                {
 0161                    return false;
 162                }
 163
 0164                if (SourceType != SourceType.Library)
 165                {
 0166                    return false;
 167                }
 168
 0169                if (this is IItemByName)
 170                {
 0171                    if (this is not IHasDualAccess hasDualAccess || hasDualAccess.IsAccessedByName)
 172                    {
 0173                        return false;
 174                    }
 175                }
 176
 0177                return true;
 178            }
 179        }
 180
 181        public static ICollectionManager CollectionManager { get; set; }
 182
 183        public override bool CanDelete()
 184        {
 6185            if (IsRoot)
 186            {
 6187                return false;
 188            }
 189
 0190            return base.CanDelete();
 191        }
 192
 193        public override bool RequiresRefresh()
 194        {
 55195            var baseResult = base.RequiresRefresh();
 196
 55197            if (SupportsCumulativeRunTimeTicks && !RunTimeTicks.HasValue)
 198            {
 0199                baseResult = true;
 200            }
 201
 55202            return baseResult;
 203        }
 204
 205        /// <summary>
 206        /// Adds the child.
 207        /// </summary>
 208        /// <param name="item">The item.</param>
 209        /// <exception cref="InvalidOperationException">Unable to add  + item.Name.</exception>
 210        public void AddChild(BaseItem item)
 211        {
 0212            item.SetParent(this);
 213
 0214            if (item.Id.IsEmpty())
 215            {
 0216                item.Id = LibraryManager.GetNewItemId(item.Path, item.GetType());
 217            }
 218
 0219            if (item.DateCreated == DateTime.MinValue)
 220            {
 0221                item.DateCreated = DateTime.UtcNow;
 222            }
 223
 0224            if (item.DateModified == DateTime.MinValue)
 225            {
 0226                item.DateModified = DateTime.UtcNow;
 227            }
 228
 0229            LibraryManager.CreateItem(item, this);
 0230        }
 231
 232        public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
 233        {
 13234            if (this is ICollectionFolder && this is not BasePluginFolder)
 235            {
 0236                var blockedMediaFolders = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedMediaFolders);
 0237                if (blockedMediaFolders.Length > 0)
 238                {
 0239                    if (blockedMediaFolders.Contains(Id))
 240                    {
 0241                        return false;
 242                    }
 243                }
 244                else
 245                {
 0246                    if (!user.HasPermission(PermissionKind.EnableAllFolders)
 0247                        && !user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(Id))
 248                    {
 0249                        return false;
 250                    }
 251                }
 252            }
 253
 13254            return base.IsVisible(user, skipAllowedTagsCheck);
 255        }
 256
 257        /// <summary>
 258        /// Loads our children.  Validation will occur externally.
 259        /// We want this synchronous.
 260        /// </summary>
 261        /// <returns>Returns children.</returns>
 262        protected virtual IReadOnlyList<BaseItem> LoadChildren()
 263        {
 264            // logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
 265            // just load our children from the repo - the library will be validated and maintained in other processes
 138266            return GetCachedChildren();
 267        }
 268
 269        public override double? GetRefreshProgress()
 270        {
 0271            return ProviderManager.GetRefreshProgress(Id);
 272        }
 273
 274        public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken)
 275        {
 0276            return ValidateChildren(progress, new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellation
 277        }
 278
 279        /// <summary>
 280        /// Validates that the children of the folder still exist.
 281        /// </summary>
 282        /// <param name="progress">The progress.</param>
 283        /// <param name="metadataRefreshOptions">The metadata refresh options.</param>
 284        /// <param name="recursive">if set to <c>true</c> [recursive].</param>
 285        /// <param name="allowRemoveRoot">remove item even this folder is root.</param>
 286        /// <param name="cancellationToken">The cancellation token.</param>
 287        /// <returns>Task.</returns>
 288        public Task ValidateChildren(IProgress<double> progress, MetadataRefreshOptions metadataRefreshOptions, bool rec
 289        {
 56290            Children = null; // invalidate cached children.
 56291            return ValidateChildrenInternal(progress, recursive, true, allowRemoveRoot, metadataRefreshOptions, metadata
 292        }
 293
 294        private Dictionary<Guid, BaseItem> GetActualChildrenDictionary()
 295        {
 53296            var dictionary = new Dictionary<Guid, BaseItem>();
 297
 53298            Children = null; // invalidate cached children.
 53299            var childrenList = Children.ToList();
 300
 190301            foreach (var child in childrenList)
 302            {
 42303                var id = child.Id;
 42304                if (dictionary.ContainsKey(id))
 305                {
 0306                    Logger.LogError(
 0307                        "Found folder containing items with duplicate id. Path: {Path}, Child Name: {ChildName}",
 0308                        Path ?? Name,
 0309                        child.Path ?? child.Name);
 310                }
 311                else
 312                {
 42313                    dictionary[id] = child;
 314                }
 315            }
 316
 53317            return dictionary;
 318        }
 319
 320        /// <summary>
 321        /// Validates the children internal.
 322        /// </summary>
 323        /// <param name="progress">The progress.</param>
 324        /// <param name="recursive">if set to <c>true</c> [recursive].</param>
 325        /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
 326        /// <param name="allowRemoveRoot">remove item even this folder is root.</param>
 327        /// <param name="refreshOptions">The refresh options.</param>
 328        /// <param name="directoryService">The directory service.</param>
 329        /// <param name="cancellationToken">The cancellation token.</param>
 330        /// <returns>Task.</returns>
 331        protected virtual async Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshCh
 332        {
 333            if (recursive)
 334            {
 335                ProviderManager.OnRefreshStart(this);
 336            }
 337
 338            try
 339            {
 340                if (GetParents().Any(f => f.Id.Equals(Id)))
 341                {
 342                    throw new InvalidOperationException("Recursive datastructure detected abort processing this item.");
 343                }
 344
 345                await ValidateChildrenInternal2(progress, recursive, refreshChildMetadata, allowRemoveRoot, refreshOptio
 346            }
 347            finally
 348            {
 349                if (recursive)
 350                {
 351                    ProviderManager.OnRefreshComplete(this);
 352                }
 353            }
 354        }
 355
 356        private static bool IsLibraryFolderAccessible(IDirectoryService directoryService, BaseItem item, bool checkColle
 357        {
 98358            if (!checkCollection && (item is BoxSet || string.Equals(item.FileNameWithoutExtension, "collections", Strin
 359            {
 0360                return true;
 361            }
 362
 363            // For top parents i.e. Library folders, skip the validation if it's empty or inaccessible
 98364            if (item.IsTopParent && !directoryService.IsAccessible(item.ContainingFolderPath))
 365            {
 35366                Logger.LogWarning("Library folder {LibraryFolderPath} is inaccessible or empty, skipping", item.Containi
 35367                return false;
 368            }
 369
 63370            return true;
 371        }
 372
 373        private async Task ValidateChildrenInternal2(IProgress<double> progress, bool recursive, bool refreshChildMetada
 374        {
 375            if (!IsLibraryFolderAccessible(directoryService, this, allowRemoveRoot))
 376            {
 377                return;
 378            }
 379
 380            cancellationToken.ThrowIfCancellationRequested();
 381
 382            var validChildren = new List<BaseItem>();
 383            var validChildrenNeedGeneration = false;
 384
 385            if (IsFileProtocol)
 386            {
 387                IEnumerable<BaseItem> nonCachedChildren = [];
 388
 389                try
 390                {
 391                    nonCachedChildren = GetNonCachedChildren(directoryService);
 392                }
 393                catch (IOException ex)
 394                {
 395                    Logger.LogError(ex, "Error retrieving children from file system");
 396                }
 397                catch (SecurityException ex)
 398                {
 399                    Logger.LogError(ex, "Error retrieving children from file system");
 400                }
 401                catch (Exception ex)
 402                {
 403                    Logger.LogError(ex, "Error retrieving children");
 404                    return;
 405                }
 406
 407                progress.Report(ProgressHelpers.RetrievedChildren);
 408
 409                if (recursive)
 410                {
 411                    ProviderManager.OnRefreshProgress(this, ProgressHelpers.RetrievedChildren);
 412                }
 413
 414                // Build a dictionary of the current children we have now by Id so we can compare quickly and easily
 415                var currentChildren = GetActualChildrenDictionary();
 416
 417                // Create a list for our validated children
 418                var newItems = new List<BaseItem>();
 419
 420                cancellationToken.ThrowIfCancellationRequested();
 421
 422                foreach (var child in nonCachedChildren)
 423                {
 424                    if (!IsLibraryFolderAccessible(directoryService, child, allowRemoveRoot))
 425                    {
 426                        continue;
 427                    }
 428
 429                    if (currentChildren.TryGetValue(child.Id, out BaseItem currentChild))
 430                    {
 431                        validChildren.Add(currentChild);
 432
 433                        if (currentChild.UpdateFromResolvedItem(child) > ItemUpdateType.None)
 434                        {
 435                            await currentChild.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken)
 436                        }
 437                        else
 438                        {
 439                            // metadata is up-to-date; make sure DB has correct images dimensions and hash
 440                            await LibraryManager.UpdateImagesAsync(currentChild).ConfigureAwait(false);
 441                        }
 442
 443                        continue;
 444                    }
 445
 446                    // Brand new item - needs to be added
 447                    child.SetParent(this);
 448                    newItems.Add(child);
 449                    validChildren.Add(child);
 450                }
 451
 452                // That's all the new and changed ones - now see if any have been removed and need cleanup
 453                var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
 454                var shouldRemove = !IsRoot || allowRemoveRoot;
 455                // If it's an AggregateFolder, don't remove
 456                if (shouldRemove && itemsRemoved.Count > 0)
 457                {
 458                    foreach (var item in itemsRemoved)
 459                    {
 460                        if (!item.CanDelete())
 461                        {
 462                            Logger.LogDebug("Item marked as non-removable, skipping: {Path}", item.Path ?? item.Name);
 463                            continue;
 464                        }
 465
 466                        if (item.IsFileProtocol)
 467                        {
 468                            Logger.LogDebug("Removed item: {Path}", item.Path);
 469
 470                            item.SetParent(null);
 471                            LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, fals
 472                        }
 473                    }
 474                }
 475
 476                if (newItems.Count > 0)
 477                {
 478                    LibraryManager.CreateItems(newItems, this, cancellationToken);
 479                }
 480            }
 481            else
 482            {
 483                validChildrenNeedGeneration = true;
 484            }
 485
 486            progress.Report(ProgressHelpers.UpdatedChildItems);
 487
 488            if (recursive)
 489            {
 490                ProviderManager.OnRefreshProgress(this, ProgressHelpers.UpdatedChildItems);
 491            }
 492
 493            cancellationToken.ThrowIfCancellationRequested();
 494
 495            if (recursive)
 496            {
 497                var folder = this;
 498                var innerProgress = new Progress<double>(innerPercent =>
 499                {
 500                    var percent = ProgressHelpers.GetProgress(ProgressHelpers.UpdatedChildItems, ProgressHelpers.Scanned
 501
 502                    progress.Report(percent);
 503
 504                    ProviderManager.OnRefreshProgress(folder, percent);
 505                });
 506
 507                if (validChildrenNeedGeneration)
 508                {
 509                    validChildren = Children.ToList();
 510                    validChildrenNeedGeneration = false;
 511                }
 512
 513                await ValidateSubFolders(validChildren.OfType<Folder>().ToList(), directoryService, innerProgress, cance
 514            }
 515
 516            if (refreshChildMetadata)
 517            {
 518                progress.Report(ProgressHelpers.ScannedSubfolders);
 519
 520                if (recursive)
 521                {
 522                    ProviderManager.OnRefreshProgress(this, ProgressHelpers.ScannedSubfolders);
 523                }
 524
 525                var container = this as IMetadataContainer;
 526
 527                var folder = this;
 528                var innerProgress = new Progress<double>(innerPercent =>
 529                {
 530                    var percent = ProgressHelpers.GetProgress(ProgressHelpers.ScannedSubfolders, ProgressHelpers.Refresh
 531
 532                    progress.Report(percent);
 533
 534                    if (recursive)
 535                    {
 536                        ProviderManager.OnRefreshProgress(folder, percent);
 537                    }
 538                });
 539
 540                if (container is not null)
 541                {
 542                    await RefreshAllMetadataForContainer(container, refreshOptions, innerProgress, cancellationToken).Co
 543                }
 544                else
 545                {
 546                    if (validChildrenNeedGeneration)
 547                    {
 548                        Children = null; // invalidate cached children.
 549                        validChildren = Children.ToList();
 550                    }
 551
 552                    await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellation
 553                }
 554            }
 555        }
 556
 557        private async Task RefreshMetadataRecursive(IList<BaseItem> children, MetadataRefreshOptions refreshOptions, boo
 558        {
 559            await RunTasks(
 560                (baseItem, innerProgress) => RefreshChildMetadata(baseItem, refreshOptions, recursive && baseItem.IsFold
 561                children,
 562                progress,
 563                cancellationToken).ConfigureAwait(false);
 564        }
 565
 566        private async Task RefreshAllMetadataForContainer(IMetadataContainer container, MetadataRefreshOptions refreshOp
 567        {
 568            if (container is Series series)
 569            {
 570                await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 571            }
 572
 573            await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false);
 574        }
 575
 576        private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, I
 577        {
 578            if (child is IMetadataContainer container)
 579            {
 580                await RefreshAllMetadataForContainer(container, refreshOptions, progress, cancellationToken).ConfigureAw
 581            }
 582            else
 583            {
 584                if (refreshOptions.RefreshItem(child))
 585                {
 586                    await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 587                }
 588
 589                if (recursive && child is Folder folder)
 590                {
 591                    folder.Children = null; // invalidate cached children.
 592                    await folder.RefreshMetadataRecursive(folder.Children.Except([this, child]).ToList(), refreshOptions
 593                }
 594            }
 595        }
 596
 597        /// <summary>
 598        /// Refreshes the children.
 599        /// </summary>
 600        /// <param name="children">The children.</param>
 601        /// <param name="directoryService">The directory service.</param>
 602        /// <param name="progress">The progress.</param>
 603        /// <param name="cancellationToken">The cancellation token.</param>
 604        /// <returns>Task.</returns>
 605        private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<doub
 606        {
 607            await RunTasks(
 608                (folder, innerProgress) => folder.ValidateChildrenInternal(innerProgress, true, false, false, null, dire
 609                children,
 610                progress,
 611                cancellationToken).ConfigureAwait(false);
 612        }
 613
 614        /// <summary>
 615        /// Runs an action block on a list of children.
 616        /// </summary>
 617        /// <param name="task">The task to run for each child.</param>
 618        /// <param name="children">The list of children.</param>
 619        /// <param name="progress">The progress.</param>
 620        /// <param name="cancellationToken">The cancellation token.</param>
 621        /// <returns>Task.</returns>
 622        private async Task RunTasks<T>(Func<T, IProgress<double>, Task> task, IList<T> children, IProgress<double> progr
 623        {
 624            await LimitedConcurrencyLibraryScheduler
 625                .Enqueue(
 626                    children.ToArray(),
 627                    task,
 628                    progress,
 629                    cancellationToken)
 630                .ConfigureAwait(false);
 631        }
 632
 633        /// <summary>
 634        /// Get the children of this folder from the actual file system.
 635        /// </summary>
 636        /// <returns>IEnumerable{BaseItem}.</returns>
 637        /// <param name="directoryService">The directory service to use for operation.</param>
 638        /// <returns>Returns set of base items.</returns>
 639        protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
 640        {
 53641            var collectionType = LibraryManager.GetContentType(this);
 53642            var libraryOptions = LibraryManager.GetLibraryOptions(this);
 643
 53644            return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, libraryO
 645        }
 646
 647        /// <summary>
 648        /// Get our children from the repo - stubbed for now.
 649        /// </summary>
 650        /// <returns>IEnumerable{BaseItem}.</returns>
 651        protected IReadOnlyList<BaseItem> GetCachedChildren()
 652        {
 138653            return ItemRepository.GetItemList(new InternalItemsQuery
 138654            {
 138655                Parent = this,
 138656                GroupByPresentationUniqueKey = false,
 138657                DtoOptions = new DtoOptions(true)
 138658            });
 659        }
 660
 661        public virtual int GetChildCount(User user)
 662        {
 0663            if (LinkedChildren.Length > 0)
 664            {
 0665                if (this is not ICollectionFolder)
 666                {
 0667                    return GetChildren(user, true).Count;
 668                }
 669            }
 670
 0671            var result = GetItems(new InternalItemsQuery(user)
 0672            {
 0673                Recursive = false,
 0674                Limit = 0,
 0675                Parent = this,
 0676                DtoOptions = new DtoOptions(false)
 0677                {
 0678                    EnableImages = false
 0679                }
 0680            });
 681
 0682            return result.TotalRecordCount;
 683        }
 684
 685        public virtual int GetRecursiveChildCount(User user)
 686        {
 0687            return GetItems(new InternalItemsQuery(user)
 0688            {
 0689                Recursive = true,
 0690                IsFolder = false,
 0691                IsVirtualItem = false,
 0692                EnableTotalRecordCount = true,
 0693                Limit = 0,
 0694                DtoOptions = new DtoOptions(false)
 0695                {
 0696                    EnableImages = false
 0697                }
 0698            }).TotalRecordCount;
 699        }
 700
 701        public QueryResult<BaseItem> QueryRecursive(InternalItemsQuery query)
 702        {
 11703            var user = query.User;
 704
 11705            if (!query.ForceDirect && RequiresPostFiltering(query))
 706            {
 707                IEnumerable<BaseItem> items;
 0708                Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManage
 709
 0710                var totalCount = 0;
 0711                if (query.User is null)
 712                {
 0713                    items = GetRecursiveChildren(filter);
 0714                    totalCount = items.Count();
 715                }
 716                else
 717                {
 718                    // Save pagination params before clearing them to prevent pagination from happening
 719                    // before sorting. PostFilterAndSort will apply pagination after sorting.
 0720                    var limit = query.Limit;
 0721                    var startIndex = query.StartIndex;
 0722                    query.Limit = null;
 0723                    query.StartIndex = null;
 724
 0725                    items = GetRecursiveChildren(user, query, out totalCount);
 726
 727                    // Restore pagination params so PostFilterAndSort can apply them after sorting
 0728                    query.Limit = limit;
 0729                    query.StartIndex = startIndex;
 730                }
 731
 0732                return PostFilterAndSort(items, query);
 733            }
 734
 11735            if (this is not UserRootFolder
 11736                && this is not AggregateFolder
 11737                && query.ParentId.IsEmpty())
 738            {
 11739                query.Parent = this;
 740            }
 741
 11742            if (RequiresPostFiltering2(query))
 743            {
 0744                return QueryWithPostFiltering2(query);
 745            }
 746
 11747            return LibraryManager.GetItemsResult(query);
 748        }
 749
 750        protected QueryResult<BaseItem> QueryWithPostFiltering2(InternalItemsQuery query)
 751        {
 1752            var startIndex = query.StartIndex;
 1753            var limit = query.Limit;
 754
 1755            query.StartIndex = null;
 1756            query.Limit = null;
 757
 1758            IEnumerable<BaseItem> itemsList = LibraryManager.GetItemList(query);
 1759            var user = query.User;
 760
 1761            if (user is not null)
 762            {
 763                // needed for boxsets
 1764                itemsList = itemsList.Where(i => i.IsVisibleStandalone(query.User));
 765            }
 766
 767            IEnumerable<BaseItem> returnItems;
 1768            int totalCount = 0;
 769
 1770            if (query.EnableTotalRecordCount)
 771            {
 0772                var itemArray = itemsList.ToArray();
 0773                totalCount = itemArray.Length;
 0774                returnItems = itemArray;
 775            }
 776            else
 777            {
 1778                returnItems = itemsList;
 779            }
 780
 1781            if (limit.HasValue)
 782            {
 0783                returnItems = returnItems.Skip(startIndex ?? 0).Take(limit.Value);
 784            }
 1785            else if (startIndex.HasValue)
 786            {
 0787                returnItems = returnItems.Skip(startIndex.Value);
 788            }
 789
 1790            return new QueryResult<BaseItem>(
 1791                query.StartIndex,
 1792                totalCount,
 1793                returnItems.ToArray());
 794        }
 795
 796        private bool RequiresPostFiltering2(InternalItemsQuery query)
 797        {
 11798            if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.BoxSet)
 799            {
 0800                Logger.LogDebug("Query requires post-filtering due to BoxSet query");
 0801                return true;
 802            }
 803
 11804            return false;
 805        }
 806
 807        private bool RequiresPostFiltering(InternalItemsQuery query)
 808        {
 11809            if (LinkedChildren.Length > 0)
 810            {
 0811                if (this is not ICollectionFolder)
 812                {
 0813                    Logger.LogDebug("{Type}: Query requires post-filtering due to LinkedChildren.", GetType().Name);
 0814                    return true;
 815                }
 816            }
 817
 818            // Filter by Video3DFormat
 11819            if (query.Is3D.HasValue)
 820            {
 0821                Logger.LogDebug("Query requires post-filtering due to Is3D");
 0822                return true;
 823            }
 824
 11825            if (query.HasOfficialRating.HasValue)
 826            {
 0827                Logger.LogDebug("Query requires post-filtering due to HasOfficialRating");
 0828                return true;
 829            }
 830
 11831            if (query.IsPlaceHolder.HasValue)
 832            {
 0833                Logger.LogDebug("Query requires post-filtering due to IsPlaceHolder");
 0834                return true;
 835            }
 836
 11837            if (query.HasSpecialFeature.HasValue)
 838            {
 0839                Logger.LogDebug("Query requires post-filtering due to HasSpecialFeature");
 0840                return true;
 841            }
 842
 11843            if (query.HasSubtitles.HasValue)
 844            {
 0845                Logger.LogDebug("Query requires post-filtering due to HasSubtitles");
 0846                return true;
 847            }
 848
 11849            if (query.HasTrailer.HasValue)
 850            {
 0851                Logger.LogDebug("Query requires post-filtering due to HasTrailer");
 0852                return true;
 853            }
 854
 11855            if (query.HasThemeSong.HasValue)
 856            {
 0857                Logger.LogDebug("Query requires post-filtering due to HasThemeSong");
 0858                return true;
 859            }
 860
 11861            if (query.HasThemeVideo.HasValue)
 862            {
 0863                Logger.LogDebug("Query requires post-filtering due to HasThemeVideo");
 0864                return true;
 865            }
 866
 867            // Filter by VideoType
 11868            if (query.VideoTypes.Length > 0)
 869            {
 0870                Logger.LogDebug("Query requires post-filtering due to VideoTypes");
 0871                return true;
 872            }
 873
 11874            if (CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
 875            {
 0876                Logger.LogDebug("Query requires post-filtering due to CollapseBoxSetItems");
 0877                return true;
 878            }
 879
 11880            if (!query.AdjacentTo.IsNullOrEmpty())
 881            {
 0882                Logger.LogDebug("Query requires post-filtering due to AdjacentTo");
 0883                return true;
 884            }
 885
 11886            if (query.SeriesStatuses.Length > 0)
 887            {
 0888                Logger.LogDebug("Query requires post-filtering due to SeriesStatuses");
 0889                return true;
 890            }
 891
 11892            if (query.AiredDuringSeason.HasValue)
 893            {
 0894                Logger.LogDebug("Query requires post-filtering due to AiredDuringSeason");
 0895                return true;
 896            }
 897
 11898            if (query.IsPlayed.HasValue)
 899            {
 0900                if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(BaseItemKind.Series))
 901                {
 0902                    Logger.LogDebug("Query requires post-filtering due to IsPlayed");
 0903                    return true;
 904                }
 905            }
 906
 11907            return false;
 908        }
 909
 910        private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
 911        {
 0912            return items.OrderBy(i => Array.IndexOf(query.ItemIds, i.Id)).ToArray();
 913        }
 914
 915        public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
 916        {
 0917            if (query.ItemIds.Length > 0)
 918            {
 0919                var result = LibraryManager.GetItemsResult(query);
 920
 0921                if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
 922                {
 0923                    result.Items = SortItemsByRequest(query, result.Items);
 924                }
 925
 0926                return result;
 927            }
 928
 0929            return GetItemsInternal(query);
 930        }
 931
 932        public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query)
 933        {
 12934            query.EnableTotalRecordCount = false;
 935
 12936            if (query.ItemIds.Length > 0)
 937            {
 0938                var result = LibraryManager.GetItemList(query);
 939
 0940                if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
 941                {
 0942                    return SortItemsByRequest(query, result);
 943                }
 944
 0945                return result;
 946            }
 947
 12948            return GetItemsInternal(query).Items;
 949        }
 950
 951        protected virtual QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
 952        {
 11953            if (SourceType == SourceType.Channel)
 954            {
 955                try
 956                {
 0957                    query.Parent = this;
 0958                    query.ChannelIds = new[] { ChannelId };
 959
 960                    // Don't blow up here because it could cause parent screens with other content to fail
 0961                    return ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None)
 962                }
 0963                catch
 964                {
 965                    // Already logged at lower levels
 0966                    return new QueryResult<BaseItem>();
 967                }
 968            }
 969
 11970            if (query.Recursive)
 971            {
 11972                return QueryRecursive(query);
 973            }
 974
 0975            var user = query.User;
 976
 0977            Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
 978
 979            IEnumerable<BaseItem> items;
 980
 0981            int totalItemCount = 0;
 0982            if (query.User is null)
 983            {
 0984                items = Children.Where(filter);
 0985                totalItemCount = items.Count();
 986            }
 987            else
 988            {
 989                // need to pass this param to the children.
 990                // Note: Don't pass Limit/StartIndex here as pagination should happen after sorting in PostFilterAndSort
 0991                var childQuery = new InternalItemsQuery
 0992                {
 0993                    DisplayAlbumFolders = query.DisplayAlbumFolders,
 0994                    NameStartsWith = query.NameStartsWith,
 0995                    NameStartsWithOrGreater = query.NameStartsWithOrGreater,
 0996                    NameLessThan = query.NameLessThan
 0997                };
 998
 0999                items = GetChildren(user, true, out totalItemCount, childQuery).Where(filter);
 1000            }
 1001
 01002            return PostFilterAndSort(items, query);
 01003        }
 1004
 1005        protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
 1006        {
 01007            var user = query.User;
 1008
 1009            // Check recursive - don't substitute in plain folder views
 01010            if (user is not null)
 1011            {
 01012                items = CollapseBoxSetItemsIfNeeded(items, query, this, user, ConfigurationManager, CollectionManager);
 1013            }
 1014
 1015#pragma warning disable CA1309
 01016            if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater))
 1017            {
 01018                items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.Inva
 1019            }
 1020
 01021            if (!string.IsNullOrEmpty(query.NameStartsWith))
 1022            {
 01023                items = items.Where(i => i.SortName.StartsWith(query.NameStartsWith, StringComparison.InvariantCultureIg
 1024            }
 1025
 01026            if (!string.IsNullOrEmpty(query.NameLessThan))
 1027            {
 01028                items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.InvariantCultur
 1029            }
 1030#pragma warning restore CA1309
 1031
 1032            // This must be the last filter
 01033            if (!query.AdjacentTo.IsNullOrEmpty())
 1034            {
 01035                items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
 1036            }
 1037
 01038            var filteredItems = items as IReadOnlyList<BaseItem> ?? items.ToList();
 01039            var result = UserViewBuilder.SortAndPage(filteredItems, null, query, LibraryManager);
 1040
 01041            if (query.EnableTotalRecordCount)
 1042            {
 01043                result.TotalRecordCount = filteredItems.Count;
 1044            }
 1045
 01046            return result;
 1047        }
 1048
 1049        private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(
 1050            IEnumerable<BaseItem> items,
 1051            InternalItemsQuery query,
 1052            BaseItem queryParent,
 1053            User user,
 1054            IServerConfigurationManager configurationManager,
 1055            ICollectionManager collectionManager)
 1056        {
 01057            ArgumentNullException.ThrowIfNull(items);
 1058
 01059            if (!CollapseBoxSetItems(query, queryParent, user, configurationManager))
 1060            {
 01061                return items;
 1062            }
 1063
 01064            var config = configurationManager.Configuration;
 1065
 01066            bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
 01067            bool collapseSeries = config.EnableGroupingShowsIntoCollections;
 1068
 01069            if (user is null || (collapseMovies && collapseSeries))
 1070            {
 01071                return collectionManager.CollapseItemsWithinBoxSets(items, user);
 1072            }
 1073
 01074            if (!collapseMovies && !collapseSeries)
 1075            {
 01076                return items;
 1077            }
 1078
 01079            var collapsibleItems = new List<BaseItem>();
 01080            var remainingItems = new List<BaseItem>();
 1081
 01082            foreach (var item in items)
 1083            {
 01084                if ((collapseMovies && item is Movie) || (collapseSeries && item is Series))
 1085                {
 01086                    collapsibleItems.Add(item);
 1087                }
 1088                else
 1089                {
 01090                    remainingItems.Add(item);
 1091                }
 1092            }
 1093
 01094            if (collapsibleItems.Count == 0)
 1095            {
 01096                return remainingItems;
 1097            }
 1098
 01099            var collapsedItems = collectionManager.CollapseItemsWithinBoxSets(collapsibleItems, user);
 1100
 01101            return collapsedItems.Concat(remainingItems);
 1102        }
 1103
 1104        private static bool CollapseBoxSetItems(
 1105            InternalItemsQuery query,
 1106            BaseItem queryParent,
 1107            User user,
 1108            IServerConfigurationManager configurationManager)
 1109        {
 1110            // Could end up stuck in a loop like this
 111111            if (queryParent is BoxSet)
 1112            {
 01113                return false;
 1114            }
 1115
 111116            if (queryParent is Season)
 1117            {
 01118                return false;
 1119            }
 1120
 111121            if (queryParent is MusicAlbum)
 1122            {
 01123                return false;
 1124            }
 1125
 111126            if (queryParent is MusicArtist)
 1127            {
 01128                return false;
 1129            }
 1130
 111131            var param = query.CollapseBoxSetItems;
 111132            if (param.HasValue)
 1133            {
 111134                return param.Value && AllowBoxSetCollapsing(query);
 1135            }
 1136
 01137            var config = configurationManager.Configuration;
 1138
 01139            bool queryHasMovies = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Mov
 01140            bool queryHasSeries = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Ser
 1141
 01142            bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
 01143            bool collapseSeries = config.EnableGroupingShowsIntoCollections;
 1144
 01145            if (user is not null)
 1146            {
 01147                bool canCollapse = (queryHasMovies && collapseMovies) || (queryHasSeries && collapseSeries);
 01148                return canCollapse && AllowBoxSetCollapsing(query);
 1149            }
 1150
 01151            return (queryHasMovies || queryHasSeries) && AllowBoxSetCollapsing(query);
 1152        }
 1153
 1154        private static bool AllowBoxSetCollapsing(InternalItemsQuery request)
 1155        {
 01156            if (request.IsFavorite.HasValue)
 1157            {
 01158                return false;
 1159            }
 1160
 01161            if (request.IsFavoriteOrLiked.HasValue)
 1162            {
 01163                return false;
 1164            }
 1165
 01166            if (request.IsLiked.HasValue)
 1167            {
 01168                return false;
 1169            }
 1170
 01171            if (request.IsPlayed.HasValue)
 1172            {
 01173                return false;
 1174            }
 1175
 01176            if (request.IsResumable.HasValue)
 1177            {
 01178                return false;
 1179            }
 1180
 01181            if (request.IsFolder.HasValue)
 1182            {
 01183                return false;
 1184            }
 1185
 01186            if (request.Genres.Count > 0)
 1187            {
 01188                return false;
 1189            }
 1190
 01191            if (request.GenreIds.Count > 0)
 1192            {
 01193                return false;
 1194            }
 1195
 01196            if (request.HasImdbId.HasValue)
 1197            {
 01198                return false;
 1199            }
 1200
 01201            if (request.HasOfficialRating.HasValue)
 1202            {
 01203                return false;
 1204            }
 1205
 01206            if (request.HasOverview.HasValue)
 1207            {
 01208                return false;
 1209            }
 1210
 01211            if (request.HasParentalRating.HasValue)
 1212            {
 01213                return false;
 1214            }
 1215
 01216            if (request.HasSpecialFeature.HasValue)
 1217            {
 01218                return false;
 1219            }
 1220
 01221            if (request.HasSubtitles.HasValue)
 1222            {
 01223                return false;
 1224            }
 1225
 01226            if (request.HasThemeSong.HasValue)
 1227            {
 01228                return false;
 1229            }
 1230
 01231            if (request.HasThemeVideo.HasValue)
 1232            {
 01233                return false;
 1234            }
 1235
 01236            if (request.HasTmdbId.HasValue)
 1237            {
 01238                return false;
 1239            }
 1240
 01241            if (request.HasTrailer.HasValue)
 1242            {
 01243                return false;
 1244            }
 1245
 01246            if (request.ImageTypes.Length > 0)
 1247            {
 01248                return false;
 1249            }
 1250
 01251            if (request.Is3D.HasValue)
 1252            {
 01253                return false;
 1254            }
 1255
 01256            if (request.Is4K.HasValue)
 1257            {
 01258                return false;
 1259            }
 1260
 01261            if (request.IsHD.HasValue)
 1262            {
 01263                return false;
 1264            }
 1265
 01266            if (request.IsLocked.HasValue)
 1267            {
 01268                return false;
 1269            }
 1270
 01271            if (request.IsPlaceHolder.HasValue)
 1272            {
 01273                return false;
 1274            }
 1275
 01276            if (!string.IsNullOrWhiteSpace(request.Person))
 1277            {
 01278                return false;
 1279            }
 1280
 01281            if (request.PersonIds.Length > 0)
 1282            {
 01283                return false;
 1284            }
 1285
 01286            if (request.ItemIds.Length > 0)
 1287            {
 01288                return false;
 1289            }
 1290
 01291            if (request.StudioIds.Length > 0)
 1292            {
 01293                return false;
 1294            }
 1295
 01296            if (request.VideoTypes.Length > 0)
 1297            {
 01298                return false;
 1299            }
 1300
 01301            if (request.Years.Length > 0)
 1302            {
 01303                return false;
 1304            }
 1305
 01306            if (request.Tags.Length > 0)
 1307            {
 01308                return false;
 1309            }
 1310
 01311            if (request.OfficialRatings.Length > 0)
 1312            {
 01313                return false;
 1314            }
 1315
 01316            if (request.MinIndexNumber.HasValue)
 1317            {
 01318                return false;
 1319            }
 1320
 01321            if (request.OrderBy.Any(o =>
 01322                o.OrderBy == ItemSortBy.CommunityRating ||
 01323                o.OrderBy == ItemSortBy.CriticRating ||
 01324                o.OrderBy == ItemSortBy.Runtime))
 1325            {
 01326                return false;
 1327            }
 1328
 01329            return true;
 1330        }
 1331
 1332        public virtual IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, out int totalItemCount
 1333        {
 101334            ArgumentNullException.ThrowIfNull(user);
 101335            query ??= new InternalItemsQuery();
 101336            query.User = user;
 1337
 1338            // the true root should return our users root folder children
 101339            if (IsPhysicalRoot)
 1340            {
 01341                return LibraryManager.GetUserRootFolder().GetChildren(user, includeLinkedChildren, out totalItemCount);
 1342            }
 1343
 101344            var result = new Dictionary<Guid, BaseItem>();
 1345
 101346            totalItemCount = AddChildren(user, includeLinkedChildren, result, false, query);
 1347
 101348            return result.Values.ToArray();
 1349        }
 1350
 1351        public virtual IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery que
 1352        {
 101353            return GetChildren(user, includeLinkedChildren, out _, query);
 1354        }
 1355
 1356        protected virtual IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
 1357        {
 101358            return Children;
 1359        }
 1360
 1361        /// <summary>
 1362        /// Adds the children to list.
 1363        /// </summary>
 1364        private int AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive
 1365        {
 1366            // Prevent infinite recursion of nested folders
 101367            visitedFolders ??= new HashSet<Folder>();
 101368            if (!visitedFolders.Add(this))
 1369            {
 01370                return 0;
 1371            }
 1372
 1373            // If Query.AlbumFolders is set, then enforce the format as per the db in that it permits sub-folders in mus
 101374            IEnumerable<BaseItem> children = null;
 101375            if ((query?.DisplayAlbumFolders ?? false) && (this is MusicAlbum))
 1376            {
 01377                children = Children;
 01378                query = null;
 1379            }
 1380
 1381            // If there are not sub-folders, proceed as normal.
 101382            if (children is null)
 1383            {
 101384                children = GetEligibleChildrenForRecursiveChildren(user);
 1385            }
 1386
 101387            if (includeLinkedChildren)
 1388            {
 101389                children = children.Concat(GetLinkedChildren(user)).ToArray();
 1390            }
 1391
 101392            return AddChildrenFromCollection(children, user, includeLinkedChildren, result, recursive, query, visitedFol
 1393        }
 1394
 1395        private int AddChildrenFromCollection(IEnumerable<BaseItem> children, User user, bool includeLinkedChildren, Dic
 1396        {
 101397            query ??= new InternalItemsQuery();
 101398            var limit = query.Limit > 0 ? query.Limit : int.MaxValue;
 101399            query.Limit = 0;
 1400
 101401            var visibleChildren = children
 101402                .Where(e => e.IsVisible(user))
 101403                .ToArray();
 1404
 101405            var realChildren = visibleChildren
 101406                .Where(e => query is null || UserViewBuilder.FilterItem(e, query))
 101407                .ToArray();
 1408
 101409            if (this is BoxSet && (query.OrderBy is null || query.OrderBy.Count == 0))
 1410            {
 01411                realChildren = realChildren
 01412                    .OrderBy(e => e.ProductionYear ?? int.MaxValue)
 01413                    .ToArray();
 1414            }
 1415
 101416            var childCount = realChildren.Length;
 101417            if (result.Count < limit)
 1418            {
 101419                var remainingCount = (int)(limit - result.Count);
 401420                foreach (var child in realChildren
 101421                    .Skip(query.StartIndex ?? 0)
 101422                    .Take(remainingCount))
 1423                {
 101424                    result[child.Id] = child;
 1425                }
 1426            }
 1427
 101428            if (recursive)
 1429            {
 01430                foreach (var child in visibleChildren
 01431                    .Where(e => e.IsFolder)
 01432                    .OfType<Folder>())
 1433                {
 01434                    childCount += child.AddChildren(user, includeLinkedChildren, result, true, query, visitedFolders);
 1435                }
 1436            }
 1437
 101438            return childCount;
 1439        }
 1440
 1441        public virtual IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query, out int totalCo
 1442        {
 01443            ArgumentNullException.ThrowIfNull(user);
 1444
 01445            var result = new Dictionary<Guid, BaseItem>();
 1446
 01447            totalCount = AddChildren(user, true, result, true, query);
 1448
 01449            return result.Values.ToArray();
 1450        }
 1451
 1452        /// <summary>
 1453        /// Gets the recursive children.
 1454        /// </summary>
 1455        /// <returns>IList{BaseItem}.</returns>
 1456        public IReadOnlyList<BaseItem> GetRecursiveChildren()
 1457        {
 01458            return GetRecursiveChildren(true);
 1459        }
 1460
 1461        public IReadOnlyList<BaseItem> GetRecursiveChildren(bool includeLinkedChildren)
 1462        {
 01463            return GetRecursiveChildren(i => true, includeLinkedChildren);
 1464        }
 1465
 1466        public IReadOnlyList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter)
 1467        {
 01468            return GetRecursiveChildren(filter, true);
 1469        }
 1470
 1471        public IReadOnlyList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter, bool includeLinkedChildren)
 1472        {
 01473            var result = new Dictionary<Guid, BaseItem>();
 1474
 01475            AddChildrenToList(result, includeLinkedChildren, true, filter);
 1476
 01477            return result.Values.ToArray();
 1478        }
 1479
 1480        /// <summary>
 1481        /// Adds the children to list.
 1482        /// </summary>
 1483        private void AddChildrenToList(Dictionary<Guid, BaseItem> result, bool includeLinkedChildren, bool recursive, Fu
 1484        {
 01485            foreach (var child in Children)
 1486            {
 01487                if (filter is null || filter(child))
 1488                {
 01489                    result[child.Id] = child;
 1490                }
 1491
 01492                if (recursive && child.IsFolder)
 1493                {
 01494                    var folder = (Folder)child;
 1495
 1496                    // We can only support includeLinkedChildren for the first folder, or we might end up stuck in a loo
 01497                    folder.AddChildrenToList(result, false, true, filter);
 1498                }
 1499            }
 1500
 01501            if (includeLinkedChildren)
 1502            {
 01503                foreach (var child in GetLinkedChildren())
 1504                {
 01505                    if (filter is null || filter(child))
 1506                    {
 01507                        result[child.Id] = child;
 1508                    }
 1509                }
 1510            }
 01511        }
 1512
 1513        /// <summary>
 1514        /// Gets the linked children.
 1515        /// </summary>
 1516        /// <returns>IEnumerable{BaseItem}.</returns>
 1517        public List<BaseItem> GetLinkedChildren()
 1518        {
 101519            var linkedChildren = LinkedChildren;
 101520            var list = new List<BaseItem>(linkedChildren.Length);
 1521
 201522            foreach (var i in linkedChildren)
 1523            {
 01524                var child = GetLinkedChild(i);
 1525
 01526                if (child is not null)
 1527                {
 01528                    list.Add(child);
 1529                }
 1530            }
 1531
 101532            return list;
 1533        }
 1534
 1535        public bool ContainsLinkedChildByItemId(Guid itemId)
 1536        {
 01537            var linkedChildren = LinkedChildren;
 01538            foreach (var i in linkedChildren)
 1539            {
 01540                if (i.ItemId.HasValue)
 1541                {
 01542                    if (i.ItemId.Value.Equals(itemId))
 1543                    {
 01544                        return true;
 1545                    }
 1546
 1547                    continue;
 1548                }
 1549
 01550                var child = GetLinkedChild(i);
 1551
 01552                if (child is not null && child.Id.Equals(itemId))
 1553                {
 01554                    return true;
 1555                }
 1556            }
 1557
 01558            return false;
 1559        }
 1560
 1561        public List<BaseItem> GetLinkedChildren(User user)
 1562        {
 101563            if (!FilterLinkedChildrenPerUser || user is null)
 1564            {
 101565                return GetLinkedChildren();
 1566            }
 1567
 01568            var linkedChildren = LinkedChildren;
 01569            var list = new List<BaseItem>(linkedChildren.Length);
 1570
 01571            if (linkedChildren.Length == 0)
 1572            {
 01573                return list;
 1574            }
 1575
 01576            var allUserRootChildren = LibraryManager.GetUserRootFolder()
 01577                .GetChildren(user, true)
 01578                .OfType<Folder>()
 01579                .ToList();
 1580
 01581            var collectionFolderIds = allUserRootChildren
 01582                .Select(i => i.Id)
 01583                .ToList();
 1584
 01585            foreach (var i in linkedChildren)
 1586            {
 01587                var child = GetLinkedChild(i);
 1588
 01589                if (child is null)
 1590                {
 1591                    continue;
 1592                }
 1593
 01594                var childOwner = child.GetOwner() ?? child;
 1595
 01596                if (child is not IItemByName)
 1597                {
 01598                    var childProtocol = childOwner.PathProtocol;
 01599                    if (!childProtocol.HasValue || childProtocol.Value != Model.MediaInfo.MediaProtocol.File)
 1600                    {
 01601                        if (!childOwner.IsVisibleStandalone(user))
 1602                        {
 01603                            continue;
 1604                        }
 1605                    }
 1606                    else
 1607                    {
 01608                        var itemCollectionFolderIds =
 01609                            LibraryManager.GetCollectionFolders(childOwner, allUserRootChildren).Select(f => f.Id);
 1610
 01611                        if (!itemCollectionFolderIds.Any(collectionFolderIds.Contains))
 1612                        {
 1613                            continue;
 1614                        }
 1615                    }
 1616                }
 1617
 01618                list.Add(child);
 1619            }
 1620
 01621            return list;
 1622        }
 1623
 1624        /// <summary>
 1625        /// Gets the linked children.
 1626        /// </summary>
 1627        /// <returns>IEnumerable{BaseItem}.</returns>
 1628        public IReadOnlyList<Tuple<LinkedChild, BaseItem>> GetLinkedChildrenInfos()
 1629        {
 01630            return LinkedChildren
 01631                .Select(i => new Tuple<LinkedChild, BaseItem>(i, GetLinkedChild(i)))
 01632                .Where(i => i.Item2 is not null)
 01633                .ToArray();
 1634        }
 1635
 1636        protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList<FileSystem
 1637        {
 1638            var changesFound = false;
 1639
 1640            if (IsFileProtocol)
 1641            {
 1642                if (RefreshLinkedChildren(fileSystemChildren))
 1643                {
 1644                    changesFound = true;
 1645                }
 1646            }
 1647
 1648            var baseHasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).Configur
 1649
 1650            return baseHasChanges || changesFound;
 1651        }
 1652
 1653        /// <summary>
 1654        /// Refreshes the linked children.
 1655        /// </summary>
 1656        /// <param name="fileSystemChildren">The enumerable of file system metadata.</param>
 1657        /// <returns><c>true</c> if the linked children were updated, <c>false</c> otherwise.</returns>
 1658        protected virtual bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
 1659        {
 211660            if (SupportsShortcutChildren)
 1661            {
 211662                var newShortcutLinks = fileSystemChildren
 211663                    .Where(i => !i.IsDirectory && FileSystem.IsShortcut(i.FullName))
 211664                    .Select(i =>
 211665                    {
 211666                        try
 211667                        {
 211668                            Logger.LogDebug("Found shortcut at {0}", i.FullName);
 211669
 211670                            var resolvedPath = CollectionFolder.ApplicationHost.ExpandVirtualPath(FileSystem.ResolveShor
 211671
 211672                            if (!string.IsNullOrEmpty(resolvedPath))
 211673                            {
 211674                                return new LinkedChild
 211675                                {
 211676                                    Path = resolvedPath,
 211677                                    Type = LinkedChildType.Shortcut
 211678                                };
 211679                            }
 211680
 211681                            Logger.LogError("Error resolving shortcut {0}", i.FullName);
 211682
 211683                            return null;
 211684                        }
 211685                        catch (IOException ex)
 211686                        {
 211687                            Logger.LogError(ex, "Error resolving shortcut {0}", i.FullName);
 211688                            return null;
 211689                        }
 211690                    })
 211691                    .Where(i => i is not null)
 211692                    .ToList();
 1693
 211694                var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
 1695
 211696                if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer(FileSystem)))
 1697                {
 01698                    Logger.LogInformation("Shortcut links have changed for {0}", Path);
 1699
 01700                    newShortcutLinks.AddRange(LinkedChildren.Where(i => i.Type == LinkedChildType.Manual));
 01701                    LinkedChildren = newShortcutLinks.ToArray();
 01702                    return true;
 1703                }
 1704            }
 1705
 421706            foreach (var child in LinkedChildren)
 1707            {
 1708                // Reset the cached value
 01709                child.ItemId = null;
 1710            }
 1711
 211712            return false;
 1713        }
 1714
 1715        /// <summary>
 1716        /// Marks the played.
 1717        /// </summary>
 1718        /// <param name="user">The user.</param>
 1719        /// <param name="datePlayed">The date played.</param>
 1720        /// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
 1721        public override void MarkPlayed(
 1722            User user,
 1723            DateTime? datePlayed,
 1724            bool resetPosition)
 1725        {
 01726            var query = new InternalItemsQuery
 01727            {
 01728                User = user,
 01729                Recursive = true,
 01730                IsFolder = false,
 01731                EnableTotalRecordCount = false
 01732            };
 1733
 01734            if (!user.DisplayMissingEpisodes)
 1735            {
 01736                query.IsVirtualItem = false;
 1737            }
 1738
 01739            var itemsResult = GetItemList(query);
 1740
 1741            // Sweep through recursively and update status
 01742            foreach (var item in itemsResult)
 1743            {
 01744                if (item.IsVirtualItem)
 1745                {
 1746                    // The querying doesn't support virtual unaired
 01747                    var episode = item as Episode;
 01748                    if (episode is not null && episode.IsUnaired)
 1749                    {
 1750                        continue;
 1751                    }
 1752                }
 1753
 01754                item.MarkPlayed(user, datePlayed, resetPosition);
 1755            }
 01756        }
 1757
 1758        /// <summary>
 1759        /// Marks the unplayed.
 1760        /// </summary>
 1761        /// <param name="user">The user.</param>
 1762        public override void MarkUnplayed(User user)
 1763        {
 01764            var itemsResult = GetItemList(new InternalItemsQuery
 01765            {
 01766                User = user,
 01767                Recursive = true,
 01768                IsFolder = false,
 01769                EnableTotalRecordCount = false
 01770            });
 1771
 1772            // Sweep through recursively and update status
 01773            foreach (var item in itemsResult)
 1774            {
 01775                item.MarkUnplayed(user);
 1776            }
 01777        }
 1778
 1779        public override bool IsPlayed(User user, UserItemData userItemData)
 1780        {
 01781            return ItemRepository.GetIsPlayed(user, Id, true);
 1782        }
 1783
 1784        public override bool IsUnplayed(User user, UserItemData userItemData)
 1785        {
 01786            return !IsPlayed(user, userItemData);
 1787        }
 1788
 1789        public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User
 1790        {
 91791            if (!SupportsUserDataFromChildren)
 1792            {
 91793                return;
 1794            }
 1795
 01796            if (itemDto is not null && fields.ContainsField(ItemFields.RecursiveItemCount))
 1797            {
 01798                itemDto.RecursiveItemCount = GetRecursiveChildCount(user);
 1799            }
 1800
 01801            if (SupportsPlayedStatus)
 1802            {
 01803                var unplayedQueryResult = GetItems(new InternalItemsQuery(user)
 01804                {
 01805                    Recursive = true,
 01806                    IsFolder = false,
 01807                    IsVirtualItem = false,
 01808                    EnableTotalRecordCount = true,
 01809                    Limit = 0,
 01810                    IsPlayed = false,
 01811                    DtoOptions = new DtoOptions(false)
 01812                    {
 01813                        EnableImages = false
 01814                    }
 01815                }).TotalRecordCount;
 1816
 01817                dto.UnplayedItemCount = unplayedQueryResult;
 1818
 01819                if (itemDto?.RecursiveItemCount > 0)
 1820                {
 01821                    var unplayedPercentage = ((double)unplayedQueryResult / itemDto.RecursiveItemCount.Value) * 100;
 01822                    dto.PlayedPercentage = 100 - unplayedPercentage;
 01823                    dto.Played = dto.PlayedPercentage.Value >= 100;
 1824                }
 1825                else
 1826                {
 01827                    dto.Played = (dto.UnplayedItemCount ?? 0) == 0;
 1828                }
 1829            }
 01830        }
 1831
 1832        /// <summary>
 1833        /// Contains constants used when reporting scan progress.
 1834        /// </summary>
 1835        private static class ProgressHelpers
 1836        {
 1837            /// <summary>
 1838            /// Reported after the folders immediate children are retrieved.
 1839            /// </summary>
 1840            public const int RetrievedChildren = 5;
 1841
 1842            /// <summary>
 1843            /// Reported after add, updating, or deleting child items from the LibraryManager.
 1844            /// </summary>
 1845            public const int UpdatedChildItems = 10;
 1846
 1847            /// <summary>
 1848            /// Reported once subfolders are scanned.
 1849            /// When scanning subfolders, the progress will be between [UpdatedItems, ScannedSubfolders].
 1850            /// </summary>
 1851            public const int ScannedSubfolders = 50;
 1852
 1853            /// <summary>
 1854            /// Reported once metadata is refreshed.
 1855            /// When refreshing metadata, the progress will be between [ScannedSubfolders, MetadataRefreshed].
 1856            /// </summary>
 1857            public const int RefreshedMetadata = 100;
 1858
 1859            /// <summary>
 1860            /// Gets the current progress given the previous step, next step, and progress in between.
 1861            /// </summary>
 1862            /// <param name="previousProgressStep">The previous progress step.</param>
 1863            /// <param name="nextProgressStep">The next progress step.</param>
 1864            /// <param name="currentProgress">The current progress step.</param>
 1865            /// <returns>The progress.</returns>
 1866            public static double GetProgress(int previousProgressStep, int nextProgressStep, double currentProgress)
 1867            {
 681868                return previousProgressStep + ((nextProgressStep - previousProgressStep) * (currentProgress / 100));
 1869            }
 1870        }
 1871    }
 1872}

Methods/Properties

.ctor()
get_SupportsThemeMedia()
get_IsPreSorted()
get_IsPhysicalRoot()
get_SupportsInheritedParentImages()
get_SupportsPlayedStatus()
get_IsFolder()
get_IsDisplayedAsFolder()
get_SupportsCumulativeRunTimeTicks()
get_SupportsDateLastMediaAdded()
get_FileNameWithoutExtension()
get_Children()
set_Children(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>)
get_RecursiveChildren()
get_SupportsShortcutChildren()
get_FilterLinkedChildrenPerUser()
get_SupportsOwnedItems()
get_SupportsUserDataFromChildren()
CanDelete()
RequiresRefresh()
AddChild(MediaBrowser.Controller.Entities.BaseItem)
IsVisible(Jellyfin.Database.Implementations.Entities.User,System.Boolean)
LoadChildren()
GetRefreshProgress()
ValidateChildren(System.IProgress`1<System.Double>,System.Threading.CancellationToken)
ValidateChildren(System.IProgress`1<System.Double>,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Boolean,System.Boolean,System.Threading.CancellationToken)
GetActualChildrenDictionary()
IsLibraryFolderAccessible(MediaBrowser.Controller.Providers.IDirectoryService,MediaBrowser.Controller.Entities.BaseItem,System.Boolean)
GetNonCachedChildren(MediaBrowser.Controller.Providers.IDirectoryService)
GetCachedChildren()
GetChildCount(Jellyfin.Database.Implementations.Entities.User)
GetRecursiveChildCount(Jellyfin.Database.Implementations.Entities.User)
QueryRecursive(MediaBrowser.Controller.Entities.InternalItemsQuery)
QueryWithPostFiltering2(MediaBrowser.Controller.Entities.InternalItemsQuery)
RequiresPostFiltering2(MediaBrowser.Controller.Entities.InternalItemsQuery)
RequiresPostFiltering(MediaBrowser.Controller.Entities.InternalItemsQuery)
SortItemsByRequest(MediaBrowser.Controller.Entities.InternalItemsQuery,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
GetItems(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetItemList(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetItemsInternal(MediaBrowser.Controller.Entities.InternalItemsQuery)
PostFilterAndSort(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>,MediaBrowser.Controller.Entities.InternalItemsQuery)
CollapseBoxSetItemsIfNeeded(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>,MediaBrowser.Controller.Entities.InternalItemsQuery,MediaBrowser.Controller.Entities.BaseItem,Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Collections.ICollectionManager)
CollapseBoxSetItems(MediaBrowser.Controller.Entities.InternalItemsQuery,MediaBrowser.Controller.Entities.BaseItem,Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Configuration.IServerConfigurationManager)
AllowBoxSetCollapsing(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetChildren(Jellyfin.Database.Implementations.Entities.User,System.Boolean,System.Int32&,MediaBrowser.Controller.Entities.InternalItemsQuery)
GetChildren(Jellyfin.Database.Implementations.Entities.User,System.Boolean,MediaBrowser.Controller.Entities.InternalItemsQuery)
GetEligibleChildrenForRecursiveChildren(Jellyfin.Database.Implementations.Entities.User)
AddChildren(Jellyfin.Database.Implementations.Entities.User,System.Boolean,System.Collections.Generic.Dictionary`2<System.Guid,MediaBrowser.Controller.Entities.BaseItem>,System.Boolean,MediaBrowser.Controller.Entities.InternalItemsQuery,System.Collections.Generic.HashSet`1<MediaBrowser.Controller.Entities.Folder>)
AddChildrenFromCollection(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>,Jellyfin.Database.Implementations.Entities.User,System.Boolean,System.Collections.Generic.Dictionary`2<System.Guid,MediaBrowser.Controller.Entities.BaseItem>,System.Boolean,MediaBrowser.Controller.Entities.InternalItemsQuery,System.Collections.Generic.HashSet`1<MediaBrowser.Controller.Entities.Folder>)
GetRecursiveChildren(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.InternalItemsQuery,System.Int32&)
GetRecursiveChildren()
GetRecursiveChildren(System.Boolean)
GetRecursiveChildren(System.Func`2<MediaBrowser.Controller.Entities.BaseItem,System.Boolean>)
GetRecursiveChildren(System.Func`2<MediaBrowser.Controller.Entities.BaseItem,System.Boolean>,System.Boolean)
AddChildrenToList(System.Collections.Generic.Dictionary`2<System.Guid,MediaBrowser.Controller.Entities.BaseItem>,System.Boolean,System.Boolean,System.Func`2<MediaBrowser.Controller.Entities.BaseItem,System.Boolean>)
GetLinkedChildren()
ContainsLinkedChildByItemId(System.Guid)
GetLinkedChildren(Jellyfin.Database.Implementations.Entities.User)
GetLinkedChildrenInfos()
RefreshLinkedChildren(System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.IO.FileSystemMetadata>)
MarkPlayed(Jellyfin.Database.Implementations.Entities.User,System.Nullable`1<System.DateTime>,System.Boolean)
MarkUnplayed(Jellyfin.Database.Implementations.Entities.User)
IsPlayed(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.UserItemData)
IsUnplayed(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.UserItemData)
FillUserDataDtoValues(MediaBrowser.Model.Dto.UserItemDataDto,MediaBrowser.Controller.Entities.UserItemData,MediaBrowser.Model.Dto.BaseItemDto,Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Dto.DtoOptions)
GetProgress(System.Int32,System.Int32,System.Double)