< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Entities.Folder
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Entities/Folder.cs
Line coverage
32%
Covered lines: 188
Uncovered lines: 399
Coverable lines: 587
Total lines: 1881
Line coverage: 32%
Branch coverage
26%
Covered branches: 102
Total branches: 392
Branch coverage: 26%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 11/15/2025 - 12:10:12 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: 187212/29/2025 - 12:13:19 AM Line coverage: 32% (188/587) Branch coverage: 26% (102/392) Total lines: 18652/15/2026 - 12:13:43 AM Line coverage: 32% (188/587) Branch coverage: 26% (102/392) Total lines: 1881 11/15/2025 - 12:10:12 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: 187212/29/2025 - 12:13:19 AM Line coverage: 32% (188/587) Branch coverage: 26% (102/392) Total lines: 18652/15/2026 - 12:13:43 AM Line coverage: 32% (188/587) Branch coverage: 26% (102/392) Total lines: 1881

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(...)57.14%151481.81%
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
 36247        public Folder()
 48        {
 36249            LinkedChildren = Array.Empty<LinkedChild>();
 36250        }
 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]
 89587        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]
 5696        public virtual bool SupportsDateLastMediaAdded => false;
 97
 98        [JsonIgnore]
 99        public override string FileNameWithoutExtension
 100        {
 101            get
 102            {
 305103                if (IsFileProtocol)
 104                {
 305105                    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        {
 1029119            get => _children ??= LoadChildren();
 222120            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]
 90136        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        {
 56195            var baseResult = base.RequiresRefresh();
 196
 56197            if (SupportsCumulativeRunTimeTicks && !RunTimeTicks.HasValue)
 198            {
 0199                baseResult = true;
 200            }
 201
 56202            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
 142266            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        {
 55296            var dictionary = new Dictionary<Guid, BaseItem>();
 297
 55298            Children = null; // invalidate cached children.
 55299            var childrenList = Children.ToList();
 300
 194301            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
 55317            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        {
 99358            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
 99364            if (item.IsTopParent && !directoryService.IsAccessible(item.ContainingFolderPath))
 365            {
 36366                Logger.LogWarning("Library folder {LibraryFolderPath} is inaccessible or empty, skipping", item.Containi
 36367                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                var actuallyRemoved = new List<BaseItem>();
 456                // If it's an AggregateFolder, don't remove
 457                if (shouldRemove && itemsRemoved.Count > 0)
 458                {
 459                    foreach (var item in itemsRemoved)
 460                    {
 461                        if (!item.CanDelete())
 462                        {
 463                            Logger.LogDebug("Item marked as non-removable, skipping: {Path}", item.Path ?? item.Name);
 464                            continue;
 465                        }
 466
 467                        if (item.IsFileProtocol)
 468                        {
 469                            Logger.LogDebug("Removed item: {Path}", item.Path);
 470
 471                            actuallyRemoved.Add(item);
 472                            item.SetParent(null);
 473                            LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, fals
 474                        }
 475                    }
 476                }
 477
 478                if (newItems.Count > 0)
 479                {
 480                    LibraryManager.CreateItems(newItems, this, cancellationToken);
 481                }
 482
 483                // After removing items, reattach any detached user data to remaining children
 484                // that share the same user data keys (eg. same episode replaced with a new file).
 485                if (actuallyRemoved.Count > 0)
 486                {
 487                    var removedKeys = actuallyRemoved.SelectMany(i => i.GetUserDataKeys()).ToHashSet();
 488                    foreach (var child in validChildren)
 489                    {
 490                        if (child.GetUserDataKeys().Any(removedKeys.Contains))
 491                        {
 492                            await child.ReattachUserDataAsync(cancellationToken).ConfigureAwait(false);
 493                        }
 494                    }
 495                }
 496            }
 497            else
 498            {
 499                validChildrenNeedGeneration = true;
 500            }
 501
 502            progress.Report(ProgressHelpers.UpdatedChildItems);
 503
 504            if (recursive)
 505            {
 506                ProviderManager.OnRefreshProgress(this, ProgressHelpers.UpdatedChildItems);
 507            }
 508
 509            cancellationToken.ThrowIfCancellationRequested();
 510
 511            if (recursive)
 512            {
 513                var folder = this;
 514                var innerProgress = new Progress<double>(innerPercent =>
 515                {
 516                    var percent = ProgressHelpers.GetProgress(ProgressHelpers.UpdatedChildItems, ProgressHelpers.Scanned
 517
 518                    progress.Report(percent);
 519
 520                    ProviderManager.OnRefreshProgress(folder, percent);
 521                });
 522
 523                if (validChildrenNeedGeneration)
 524                {
 525                    validChildren = Children.ToList();
 526                    validChildrenNeedGeneration = false;
 527                }
 528
 529                await ValidateSubFolders(validChildren.OfType<Folder>().ToList(), directoryService, innerProgress, cance
 530            }
 531
 532            if (refreshChildMetadata)
 533            {
 534                progress.Report(ProgressHelpers.ScannedSubfolders);
 535
 536                if (recursive)
 537                {
 538                    ProviderManager.OnRefreshProgress(this, ProgressHelpers.ScannedSubfolders);
 539                }
 540
 541                var container = this as IMetadataContainer;
 542
 543                var folder = this;
 544                var innerProgress = new Progress<double>(innerPercent =>
 545                {
 546                    var percent = ProgressHelpers.GetProgress(ProgressHelpers.ScannedSubfolders, ProgressHelpers.Refresh
 547
 548                    progress.Report(percent);
 549
 550                    if (recursive)
 551                    {
 552                        ProviderManager.OnRefreshProgress(folder, percent);
 553                    }
 554                });
 555
 556                if (container is not null)
 557                {
 558                    await RefreshAllMetadataForContainer(container, refreshOptions, innerProgress, cancellationToken).Co
 559                }
 560                else
 561                {
 562                    if (validChildrenNeedGeneration)
 563                    {
 564                        Children = null; // invalidate cached children.
 565                        validChildren = Children.ToList();
 566                    }
 567
 568                    await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellation
 569                }
 570            }
 571        }
 572
 573        private async Task RefreshMetadataRecursive(IList<BaseItem> children, MetadataRefreshOptions refreshOptions, boo
 574        {
 575            await RunTasks(
 576                (baseItem, innerProgress) => RefreshChildMetadata(baseItem, refreshOptions, recursive && baseItem.IsFold
 577                children,
 578                progress,
 579                cancellationToken).ConfigureAwait(false);
 580        }
 581
 582        private async Task RefreshAllMetadataForContainer(IMetadataContainer container, MetadataRefreshOptions refreshOp
 583        {
 584            if (container is Series series)
 585            {
 586                await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 587            }
 588
 589            await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false);
 590        }
 591
 592        private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, I
 593        {
 594            if (child is IMetadataContainer container)
 595            {
 596                await RefreshAllMetadataForContainer(container, refreshOptions, progress, cancellationToken).ConfigureAw
 597            }
 598            else
 599            {
 600                if (refreshOptions.RefreshItem(child))
 601                {
 602                    await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 603                }
 604
 605                if (recursive && child is Folder folder)
 606                {
 607                    folder.Children = null; // invalidate cached children.
 608                    await folder.RefreshMetadataRecursive(folder.Children.Except([this, child]).ToList(), refreshOptions
 609                }
 610            }
 611        }
 612
 613        /// <summary>
 614        /// Refreshes the children.
 615        /// </summary>
 616        /// <param name="children">The children.</param>
 617        /// <param name="directoryService">The directory service.</param>
 618        /// <param name="progress">The progress.</param>
 619        /// <param name="cancellationToken">The cancellation token.</param>
 620        /// <returns>Task.</returns>
 621        private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<doub
 622        {
 623            await RunTasks(
 624                (folder, innerProgress) => folder.ValidateChildrenInternal(innerProgress, true, false, false, null, dire
 625                children,
 626                progress,
 627                cancellationToken).ConfigureAwait(false);
 628        }
 629
 630        /// <summary>
 631        /// Runs an action block on a list of children.
 632        /// </summary>
 633        /// <param name="task">The task to run for each child.</param>
 634        /// <param name="children">The list of children.</param>
 635        /// <param name="progress">The progress.</param>
 636        /// <param name="cancellationToken">The cancellation token.</param>
 637        /// <returns>Task.</returns>
 638        private async Task RunTasks<T>(Func<T, IProgress<double>, Task> task, IList<T> children, IProgress<double> progr
 639        {
 640            await LimitedConcurrencyLibraryScheduler
 641                .Enqueue(
 642                    children.ToArray(),
 643                    task,
 644                    progress,
 645                    cancellationToken)
 646                .ConfigureAwait(false);
 647        }
 648
 649        /// <summary>
 650        /// Get the children of this folder from the actual file system.
 651        /// </summary>
 652        /// <returns>IEnumerable{BaseItem}.</returns>
 653        /// <param name="directoryService">The directory service to use for operation.</param>
 654        /// <returns>Returns set of base items.</returns>
 655        protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
 656        {
 55657            var collectionType = LibraryManager.GetContentType(this);
 55658            var libraryOptions = LibraryManager.GetLibraryOptions(this);
 659
 55660            return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, libraryO
 661        }
 662
 663        /// <summary>
 664        /// Get our children from the repo - stubbed for now.
 665        /// </summary>
 666        /// <returns>IEnumerable{BaseItem}.</returns>
 667        protected IReadOnlyList<BaseItem> GetCachedChildren()
 668        {
 142669            return ItemRepository.GetItemList(new InternalItemsQuery
 142670            {
 142671                Parent = this,
 142672                GroupByPresentationUniqueKey = false,
 142673                DtoOptions = new DtoOptions(true)
 142674            });
 675        }
 676
 677        public virtual int GetChildCount(User user)
 678        {
 0679            if (LinkedChildren.Length > 0)
 680            {
 0681                if (this is not ICollectionFolder)
 682                {
 0683                    return GetChildren(user, true).Count;
 684                }
 685            }
 686
 0687            var result = GetItems(new InternalItemsQuery(user)
 0688            {
 0689                Recursive = false,
 0690                Limit = 0,
 0691                Parent = this,
 0692                DtoOptions = new DtoOptions(false)
 0693                {
 0694                    EnableImages = false
 0695                }
 0696            });
 697
 0698            return result.TotalRecordCount;
 699        }
 700
 701        public virtual int GetRecursiveChildCount(User user)
 702        {
 0703            return GetItems(new InternalItemsQuery(user)
 0704            {
 0705                Recursive = true,
 0706                IsFolder = false,
 0707                IsVirtualItem = false,
 0708                EnableTotalRecordCount = true,
 0709                Limit = 0,
 0710                DtoOptions = new DtoOptions(false)
 0711                {
 0712                    EnableImages = false
 0713                }
 0714            }).TotalRecordCount;
 715        }
 716
 717        public QueryResult<BaseItem> QueryRecursive(InternalItemsQuery query)
 718        {
 14719            var user = query.User;
 720
 14721            if (!query.ForceDirect && RequiresPostFiltering(query))
 722            {
 723                IEnumerable<BaseItem> items;
 0724                Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManage
 725
 0726                var totalCount = 0;
 0727                if (query.User is null)
 728                {
 0729                    items = GetRecursiveChildren(filter);
 0730                    totalCount = items.Count();
 731                }
 732                else
 733                {
 734                    // Save pagination params before clearing them to prevent pagination from happening
 735                    // before sorting. PostFilterAndSort will apply pagination after sorting.
 0736                    var limit = query.Limit;
 0737                    var startIndex = query.StartIndex;
 0738                    query.Limit = null;
 0739                    query.StartIndex = null;
 740
 0741                    items = GetRecursiveChildren(user, query, out totalCount);
 742
 743                    // Restore pagination params so PostFilterAndSort can apply them after sorting
 0744                    query.Limit = limit;
 0745                    query.StartIndex = startIndex;
 746                }
 747
 0748                return PostFilterAndSort(items, query);
 749            }
 750
 14751            if (this is not UserRootFolder
 14752                && this is not AggregateFolder
 14753                && query.ParentId.IsEmpty())
 754            {
 14755                query.Parent = this;
 756            }
 757
 14758            if (RequiresPostFiltering2(query))
 759            {
 0760                return QueryWithPostFiltering2(query);
 761            }
 762
 14763            return LibraryManager.GetItemsResult(query);
 764        }
 765
 766        protected QueryResult<BaseItem> QueryWithPostFiltering2(InternalItemsQuery query)
 767        {
 1768            var startIndex = query.StartIndex;
 1769            var limit = query.Limit;
 770
 1771            query.StartIndex = null;
 1772            query.Limit = null;
 773
 1774            IEnumerable<BaseItem> itemsList = LibraryManager.GetItemList(query);
 1775            var user = query.User;
 776
 1777            if (user is not null)
 778            {
 779                // needed for boxsets
 1780                itemsList = itemsList.Where(i => i.IsVisibleStandalone(query.User));
 781            }
 782
 783            IEnumerable<BaseItem> returnItems;
 1784            int totalCount = 0;
 785
 1786            if (query.EnableTotalRecordCount)
 787            {
 0788                var itemArray = itemsList.ToArray();
 0789                totalCount = itemArray.Length;
 0790                returnItems = itemArray;
 791            }
 792            else
 793            {
 1794                returnItems = itemsList;
 795            }
 796
 1797            if (limit.HasValue)
 798            {
 0799                returnItems = returnItems.Skip(startIndex ?? 0).Take(limit.Value);
 800            }
 1801            else if (startIndex.HasValue)
 802            {
 0803                returnItems = returnItems.Skip(startIndex.Value);
 804            }
 805
 1806            return new QueryResult<BaseItem>(
 1807                query.StartIndex,
 1808                totalCount,
 1809                returnItems.ToArray());
 810        }
 811
 812        private bool RequiresPostFiltering2(InternalItemsQuery query)
 813        {
 14814            if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.BoxSet)
 815            {
 0816                Logger.LogDebug("Query requires post-filtering due to BoxSet query");
 0817                return true;
 818            }
 819
 14820            return false;
 821        }
 822
 823        private bool RequiresPostFiltering(InternalItemsQuery query)
 824        {
 14825            if (LinkedChildren.Length > 0)
 826            {
 0827                if (this is not ICollectionFolder)
 828                {
 0829                    Logger.LogDebug("{Type}: Query requires post-filtering due to LinkedChildren.", GetType().Name);
 0830                    return true;
 831                }
 832            }
 833
 834            // Filter by Video3DFormat
 14835            if (query.Is3D.HasValue)
 836            {
 0837                Logger.LogDebug("Query requires post-filtering due to Is3D");
 0838                return true;
 839            }
 840
 14841            if (query.HasOfficialRating.HasValue)
 842            {
 0843                Logger.LogDebug("Query requires post-filtering due to HasOfficialRating");
 0844                return true;
 845            }
 846
 14847            if (query.IsPlaceHolder.HasValue)
 848            {
 0849                Logger.LogDebug("Query requires post-filtering due to IsPlaceHolder");
 0850                return true;
 851            }
 852
 14853            if (query.HasSpecialFeature.HasValue)
 854            {
 0855                Logger.LogDebug("Query requires post-filtering due to HasSpecialFeature");
 0856                return true;
 857            }
 858
 14859            if (query.HasSubtitles.HasValue)
 860            {
 0861                Logger.LogDebug("Query requires post-filtering due to HasSubtitles");
 0862                return true;
 863            }
 864
 14865            if (query.HasTrailer.HasValue)
 866            {
 0867                Logger.LogDebug("Query requires post-filtering due to HasTrailer");
 0868                return true;
 869            }
 870
 14871            if (query.HasThemeSong.HasValue)
 872            {
 0873                Logger.LogDebug("Query requires post-filtering due to HasThemeSong");
 0874                return true;
 875            }
 876
 14877            if (query.HasThemeVideo.HasValue)
 878            {
 0879                Logger.LogDebug("Query requires post-filtering due to HasThemeVideo");
 0880                return true;
 881            }
 882
 883            // Filter by VideoType
 14884            if (query.VideoTypes.Length > 0)
 885            {
 0886                Logger.LogDebug("Query requires post-filtering due to VideoTypes");
 0887                return true;
 888            }
 889
 14890            if (CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
 891            {
 0892                Logger.LogDebug("Query requires post-filtering due to CollapseBoxSetItems");
 0893                return true;
 894            }
 895
 14896            if (!query.AdjacentTo.IsNullOrEmpty())
 897            {
 0898                Logger.LogDebug("Query requires post-filtering due to AdjacentTo");
 0899                return true;
 900            }
 901
 14902            if (query.SeriesStatuses.Length > 0)
 903            {
 0904                Logger.LogDebug("Query requires post-filtering due to SeriesStatuses");
 0905                return true;
 906            }
 907
 14908            if (query.AiredDuringSeason.HasValue)
 909            {
 0910                Logger.LogDebug("Query requires post-filtering due to AiredDuringSeason");
 0911                return true;
 912            }
 913
 14914            if (query.IsPlayed.HasValue)
 915            {
 0916                if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(BaseItemKind.Series))
 917                {
 0918                    Logger.LogDebug("Query requires post-filtering due to IsPlayed");
 0919                    return true;
 920                }
 921            }
 922
 14923            return false;
 924        }
 925
 926        private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
 927        {
 0928            return items.OrderBy(i => Array.IndexOf(query.ItemIds, i.Id)).ToArray();
 929        }
 930
 931        public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
 932        {
 0933            if (query.ItemIds.Length > 0)
 934            {
 0935                var result = LibraryManager.GetItemsResult(query);
 936
 0937                if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
 938                {
 0939                    result.Items = SortItemsByRequest(query, result.Items);
 940                }
 941
 0942                return result;
 943            }
 944
 0945            return GetItemsInternal(query);
 946        }
 947
 948        public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query)
 949        {
 15950            query.EnableTotalRecordCount = false;
 951
 15952            if (query.ItemIds.Length > 0)
 953            {
 0954                var result = LibraryManager.GetItemList(query);
 955
 0956                if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
 957                {
 0958                    return SortItemsByRequest(query, result);
 959                }
 960
 0961                return result;
 962            }
 963
 15964            return GetItemsInternal(query).Items;
 965        }
 966
 967        protected virtual QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
 968        {
 14969            if (SourceType == SourceType.Channel)
 970            {
 971                try
 972                {
 0973                    query.Parent = this;
 0974                    query.ChannelIds = new[] { ChannelId };
 975
 976                    // Don't blow up here because it could cause parent screens with other content to fail
 0977                    return ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None)
 978                }
 0979                catch
 980                {
 981                    // Already logged at lower levels
 0982                    return new QueryResult<BaseItem>();
 983                }
 984            }
 985
 14986            if (query.Recursive)
 987            {
 14988                return QueryRecursive(query);
 989            }
 990
 0991            var user = query.User;
 992
 0993            Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
 994
 995            IEnumerable<BaseItem> items;
 996
 0997            int totalItemCount = 0;
 0998            if (query.User is null)
 999            {
 01000                items = Children.Where(filter);
 01001                totalItemCount = items.Count();
 1002            }
 1003            else
 1004            {
 1005                // need to pass this param to the children.
 1006                // Note: Don't pass Limit/StartIndex here as pagination should happen after sorting in PostFilterAndSort
 01007                var childQuery = new InternalItemsQuery
 01008                {
 01009                    DisplayAlbumFolders = query.DisplayAlbumFolders,
 01010                    NameStartsWith = query.NameStartsWith,
 01011                    NameStartsWithOrGreater = query.NameStartsWithOrGreater,
 01012                    NameLessThan = query.NameLessThan
 01013                };
 1014
 01015                items = GetChildren(user, true, out totalItemCount, childQuery).Where(filter);
 1016            }
 1017
 01018            return PostFilterAndSort(items, query);
 01019        }
 1020
 1021        protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
 1022        {
 01023            var user = query.User;
 1024
 1025            // Check recursive - don't substitute in plain folder views
 01026            if (user is not null)
 1027            {
 01028                items = CollapseBoxSetItemsIfNeeded(items, query, this, user, ConfigurationManager, CollectionManager);
 1029            }
 1030
 1031#pragma warning disable CA1309
 01032            if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater))
 1033            {
 01034                items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.Inva
 1035            }
 1036
 01037            if (!string.IsNullOrEmpty(query.NameStartsWith))
 1038            {
 01039                items = items.Where(i => i.SortName.StartsWith(query.NameStartsWith, StringComparison.InvariantCultureIg
 1040            }
 1041
 01042            if (!string.IsNullOrEmpty(query.NameLessThan))
 1043            {
 01044                items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.InvariantCultur
 1045            }
 1046#pragma warning restore CA1309
 1047
 1048            // This must be the last filter
 01049            if (!query.AdjacentTo.IsNullOrEmpty())
 1050            {
 01051                items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
 1052            }
 1053
 01054            var filteredItems = items as IReadOnlyList<BaseItem> ?? items.ToList();
 01055            var result = UserViewBuilder.SortAndPage(filteredItems, null, query, LibraryManager);
 1056
 01057            if (query.EnableTotalRecordCount)
 1058            {
 01059                result.TotalRecordCount = filteredItems.Count;
 1060            }
 1061
 01062            return result;
 1063        }
 1064
 1065        private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(
 1066            IEnumerable<BaseItem> items,
 1067            InternalItemsQuery query,
 1068            BaseItem queryParent,
 1069            User user,
 1070            IServerConfigurationManager configurationManager,
 1071            ICollectionManager collectionManager)
 1072        {
 01073            ArgumentNullException.ThrowIfNull(items);
 1074
 01075            if (!CollapseBoxSetItems(query, queryParent, user, configurationManager))
 1076            {
 01077                return items;
 1078            }
 1079
 01080            var config = configurationManager.Configuration;
 1081
 01082            bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
 01083            bool collapseSeries = config.EnableGroupingShowsIntoCollections;
 1084
 01085            if (user is null || (collapseMovies && collapseSeries))
 1086            {
 01087                return collectionManager.CollapseItemsWithinBoxSets(items, user);
 1088            }
 1089
 01090            if (!collapseMovies && !collapseSeries)
 1091            {
 01092                return items;
 1093            }
 1094
 01095            var collapsibleItems = new List<BaseItem>();
 01096            var remainingItems = new List<BaseItem>();
 1097
 01098            foreach (var item in items)
 1099            {
 01100                if ((collapseMovies && item is Movie) || (collapseSeries && item is Series))
 1101                {
 01102                    collapsibleItems.Add(item);
 1103                }
 1104                else
 1105                {
 01106                    remainingItems.Add(item);
 1107                }
 1108            }
 1109
 01110            if (collapsibleItems.Count == 0)
 1111            {
 01112                return remainingItems;
 1113            }
 1114
 01115            var collapsedItems = collectionManager.CollapseItemsWithinBoxSets(collapsibleItems, user);
 1116
 01117            return collapsedItems.Concat(remainingItems);
 1118        }
 1119
 1120        private static bool CollapseBoxSetItems(
 1121            InternalItemsQuery query,
 1122            BaseItem queryParent,
 1123            User user,
 1124            IServerConfigurationManager configurationManager)
 1125        {
 1126            // Could end up stuck in a loop like this
 141127            if (queryParent is BoxSet)
 1128            {
 01129                return false;
 1130            }
 1131
 141132            if (queryParent is Season)
 1133            {
 01134                return false;
 1135            }
 1136
 141137            if (queryParent is MusicAlbum)
 1138            {
 01139                return false;
 1140            }
 1141
 141142            if (queryParent is MusicArtist)
 1143            {
 01144                return false;
 1145            }
 1146
 141147            var param = query.CollapseBoxSetItems;
 141148            if (param.HasValue)
 1149            {
 141150                return param.Value && AllowBoxSetCollapsing(query);
 1151            }
 1152
 01153            var config = configurationManager.Configuration;
 1154
 01155            bool queryHasMovies = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Mov
 01156            bool queryHasSeries = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Ser
 1157
 01158            bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
 01159            bool collapseSeries = config.EnableGroupingShowsIntoCollections;
 1160
 01161            if (user is not null)
 1162            {
 01163                bool canCollapse = (queryHasMovies && collapseMovies) || (queryHasSeries && collapseSeries);
 01164                return canCollapse && AllowBoxSetCollapsing(query);
 1165            }
 1166
 01167            return (queryHasMovies || queryHasSeries) && AllowBoxSetCollapsing(query);
 1168        }
 1169
 1170        private static bool AllowBoxSetCollapsing(InternalItemsQuery request)
 1171        {
 01172            if (request.IsFavorite.HasValue)
 1173            {
 01174                return false;
 1175            }
 1176
 01177            if (request.IsFavoriteOrLiked.HasValue)
 1178            {
 01179                return false;
 1180            }
 1181
 01182            if (request.IsLiked.HasValue)
 1183            {
 01184                return false;
 1185            }
 1186
 01187            if (request.IsPlayed.HasValue)
 1188            {
 01189                return false;
 1190            }
 1191
 01192            if (request.IsResumable.HasValue)
 1193            {
 01194                return false;
 1195            }
 1196
 01197            if (request.IsFolder.HasValue)
 1198            {
 01199                return false;
 1200            }
 1201
 01202            if (request.Genres.Count > 0)
 1203            {
 01204                return false;
 1205            }
 1206
 01207            if (request.GenreIds.Count > 0)
 1208            {
 01209                return false;
 1210            }
 1211
 01212            if (request.HasImdbId.HasValue)
 1213            {
 01214                return false;
 1215            }
 1216
 01217            if (request.HasOfficialRating.HasValue)
 1218            {
 01219                return false;
 1220            }
 1221
 01222            if (request.HasOverview.HasValue)
 1223            {
 01224                return false;
 1225            }
 1226
 01227            if (request.HasParentalRating.HasValue)
 1228            {
 01229                return false;
 1230            }
 1231
 01232            if (request.HasSpecialFeature.HasValue)
 1233            {
 01234                return false;
 1235            }
 1236
 01237            if (request.HasSubtitles.HasValue)
 1238            {
 01239                return false;
 1240            }
 1241
 01242            if (request.HasThemeSong.HasValue)
 1243            {
 01244                return false;
 1245            }
 1246
 01247            if (request.HasThemeVideo.HasValue)
 1248            {
 01249                return false;
 1250            }
 1251
 01252            if (request.HasTmdbId.HasValue)
 1253            {
 01254                return false;
 1255            }
 1256
 01257            if (request.HasTrailer.HasValue)
 1258            {
 01259                return false;
 1260            }
 1261
 01262            if (request.ImageTypes.Length > 0)
 1263            {
 01264                return false;
 1265            }
 1266
 01267            if (request.Is3D.HasValue)
 1268            {
 01269                return false;
 1270            }
 1271
 01272            if (request.Is4K.HasValue)
 1273            {
 01274                return false;
 1275            }
 1276
 01277            if (request.IsHD.HasValue)
 1278            {
 01279                return false;
 1280            }
 1281
 01282            if (request.IsLocked.HasValue)
 1283            {
 01284                return false;
 1285            }
 1286
 01287            if (request.IsPlaceHolder.HasValue)
 1288            {
 01289                return false;
 1290            }
 1291
 01292            if (!string.IsNullOrWhiteSpace(request.Person))
 1293            {
 01294                return false;
 1295            }
 1296
 01297            if (request.PersonIds.Length > 0)
 1298            {
 01299                return false;
 1300            }
 1301
 01302            if (request.ItemIds.Length > 0)
 1303            {
 01304                return false;
 1305            }
 1306
 01307            if (request.StudioIds.Length > 0)
 1308            {
 01309                return false;
 1310            }
 1311
 01312            if (request.VideoTypes.Length > 0)
 1313            {
 01314                return false;
 1315            }
 1316
 01317            if (request.Years.Length > 0)
 1318            {
 01319                return false;
 1320            }
 1321
 01322            if (request.Tags.Length > 0)
 1323            {
 01324                return false;
 1325            }
 1326
 01327            if (request.OfficialRatings.Length > 0)
 1328            {
 01329                return false;
 1330            }
 1331
 01332            if (request.MinIndexNumber.HasValue)
 1333            {
 01334                return false;
 1335            }
 1336
 01337            if (request.OrderBy.Any(o =>
 01338                o.OrderBy == ItemSortBy.CommunityRating ||
 01339                o.OrderBy == ItemSortBy.CriticRating ||
 01340                o.OrderBy == ItemSortBy.Runtime))
 1341            {
 01342                return false;
 1343            }
 1344
 01345            return true;
 1346        }
 1347
 1348        public virtual IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, out int totalItemCount
 1349        {
 101350            ArgumentNullException.ThrowIfNull(user);
 101351            query ??= new InternalItemsQuery();
 101352            query.User = user;
 1353
 1354            // the true root should return our users root folder children
 101355            if (IsPhysicalRoot)
 1356            {
 01357                return LibraryManager.GetUserRootFolder().GetChildren(user, includeLinkedChildren, out totalItemCount);
 1358            }
 1359
 101360            var result = new Dictionary<Guid, BaseItem>();
 1361
 101362            totalItemCount = AddChildren(user, includeLinkedChildren, result, false, query);
 1363
 101364            return result.Values.ToArray();
 1365        }
 1366
 1367        public virtual IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery que
 1368        {
 101369            return GetChildren(user, includeLinkedChildren, out _, query);
 1370        }
 1371
 1372        protected virtual IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
 1373        {
 101374            return Children;
 1375        }
 1376
 1377        /// <summary>
 1378        /// Adds the children to list.
 1379        /// </summary>
 1380        private int AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive
 1381        {
 1382            // Prevent infinite recursion of nested folders
 101383            visitedFolders ??= new HashSet<Folder>();
 101384            if (!visitedFolders.Add(this))
 1385            {
 01386                return 0;
 1387            }
 1388
 1389            // If Query.AlbumFolders is set, then enforce the format as per the db in that it permits sub-folders in mus
 101390            IEnumerable<BaseItem> children = null;
 101391            if ((query?.DisplayAlbumFolders ?? false) && (this is MusicAlbum))
 1392            {
 01393                children = Children;
 01394                query = null;
 1395            }
 1396
 1397            // If there are not sub-folders, proceed as normal.
 101398            if (children is null)
 1399            {
 101400                children = GetEligibleChildrenForRecursiveChildren(user);
 1401            }
 1402
 101403            if (includeLinkedChildren)
 1404            {
 101405                children = children.Concat(GetLinkedChildren(user)).ToArray();
 1406            }
 1407
 101408            return AddChildrenFromCollection(children, user, includeLinkedChildren, result, recursive, query, visitedFol
 1409        }
 1410
 1411        private int AddChildrenFromCollection(IEnumerable<BaseItem> children, User user, bool includeLinkedChildren, Dic
 1412        {
 101413            query ??= new InternalItemsQuery();
 101414            var limit = query.Limit > 0 ? query.Limit : int.MaxValue;
 101415            query.Limit = 0;
 1416
 101417            var visibleChildren = children
 101418                .Where(e => e.IsVisible(user))
 101419                .ToArray();
 1420
 101421            var realChildren = visibleChildren
 101422                .Where(e => query is null || UserViewBuilder.FilterItem(e, query))
 101423                .ToArray();
 1424
 101425            var childCount = realChildren.Length;
 101426            if (result.Count < limit)
 1427            {
 101428                var remainingCount = (int)(limit - result.Count);
 401429                foreach (var child in realChildren
 101430                    .Skip(query.StartIndex ?? 0)
 101431                    .Take(remainingCount))
 1432                {
 101433                    result[child.Id] = child;
 1434                }
 1435            }
 1436
 101437            if (recursive)
 1438            {
 01439                foreach (var child in visibleChildren
 01440                    .Where(e => e.IsFolder)
 01441                    .OfType<Folder>())
 1442                {
 01443                    childCount += child.AddChildren(user, includeLinkedChildren, result, true, query, visitedFolders);
 1444                }
 1445            }
 1446
 101447            return childCount;
 1448        }
 1449
 1450        public virtual IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query, out int totalCo
 1451        {
 01452            ArgumentNullException.ThrowIfNull(user);
 1453
 01454            var result = new Dictionary<Guid, BaseItem>();
 1455
 01456            totalCount = AddChildren(user, true, result, true, query);
 1457
 01458            return result.Values.ToArray();
 1459        }
 1460
 1461        /// <summary>
 1462        /// Gets the recursive children.
 1463        /// </summary>
 1464        /// <returns>IList{BaseItem}.</returns>
 1465        public IReadOnlyList<BaseItem> GetRecursiveChildren()
 1466        {
 01467            return GetRecursiveChildren(true);
 1468        }
 1469
 1470        public IReadOnlyList<BaseItem> GetRecursiveChildren(bool includeLinkedChildren)
 1471        {
 01472            return GetRecursiveChildren(i => true, includeLinkedChildren);
 1473        }
 1474
 1475        public IReadOnlyList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter)
 1476        {
 01477            return GetRecursiveChildren(filter, true);
 1478        }
 1479
 1480        public IReadOnlyList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter, bool includeLinkedChildren)
 1481        {
 01482            var result = new Dictionary<Guid, BaseItem>();
 1483
 01484            AddChildrenToList(result, includeLinkedChildren, true, filter);
 1485
 01486            return result.Values.ToArray();
 1487        }
 1488
 1489        /// <summary>
 1490        /// Adds the children to list.
 1491        /// </summary>
 1492        private void AddChildrenToList(Dictionary<Guid, BaseItem> result, bool includeLinkedChildren, bool recursive, Fu
 1493        {
 01494            foreach (var child in Children)
 1495            {
 01496                if (filter is null || filter(child))
 1497                {
 01498                    result[child.Id] = child;
 1499                }
 1500
 01501                if (recursive && child.IsFolder)
 1502                {
 01503                    var folder = (Folder)child;
 1504
 1505                    // We can only support includeLinkedChildren for the first folder, or we might end up stuck in a loo
 01506                    folder.AddChildrenToList(result, false, true, filter);
 1507                }
 1508            }
 1509
 01510            if (includeLinkedChildren)
 1511            {
 01512                foreach (var child in GetLinkedChildren())
 1513                {
 01514                    if (filter is null || filter(child))
 1515                    {
 01516                        result[child.Id] = child;
 1517                    }
 1518                }
 1519            }
 01520        }
 1521
 1522        /// <summary>
 1523        /// Gets the linked children.
 1524        /// </summary>
 1525        /// <returns>IEnumerable{BaseItem}.</returns>
 1526        public List<BaseItem> GetLinkedChildren()
 1527        {
 101528            var linkedChildren = LinkedChildren;
 101529            var list = new List<BaseItem>(linkedChildren.Length);
 1530
 201531            foreach (var i in linkedChildren)
 1532            {
 01533                var child = GetLinkedChild(i);
 1534
 01535                if (child is not null)
 1536                {
 01537                    list.Add(child);
 1538                }
 1539            }
 1540
 101541            return list;
 1542        }
 1543
 1544        public bool ContainsLinkedChildByItemId(Guid itemId)
 1545        {
 01546            var linkedChildren = LinkedChildren;
 01547            foreach (var i in linkedChildren)
 1548            {
 01549                if (i.ItemId.HasValue)
 1550                {
 01551                    if (i.ItemId.Value.Equals(itemId))
 1552                    {
 01553                        return true;
 1554                    }
 1555
 1556                    continue;
 1557                }
 1558
 01559                var child = GetLinkedChild(i);
 1560
 01561                if (child is not null && child.Id.Equals(itemId))
 1562                {
 01563                    return true;
 1564                }
 1565            }
 1566
 01567            return false;
 1568        }
 1569
 1570        public List<BaseItem> GetLinkedChildren(User user)
 1571        {
 101572            if (!FilterLinkedChildrenPerUser || user is null)
 1573            {
 101574                return GetLinkedChildren();
 1575            }
 1576
 01577            var linkedChildren = LinkedChildren;
 01578            var list = new List<BaseItem>(linkedChildren.Length);
 1579
 01580            if (linkedChildren.Length == 0)
 1581            {
 01582                return list;
 1583            }
 1584
 01585            var allUserRootChildren = LibraryManager.GetUserRootFolder()
 01586                .GetChildren(user, true)
 01587                .OfType<Folder>()
 01588                .ToList();
 1589
 01590            var collectionFolderIds = allUserRootChildren
 01591                .Select(i => i.Id)
 01592                .ToList();
 1593
 01594            foreach (var i in linkedChildren)
 1595            {
 01596                var child = GetLinkedChild(i);
 1597
 01598                if (child is null)
 1599                {
 1600                    continue;
 1601                }
 1602
 01603                var childOwner = child.GetOwner() ?? child;
 1604
 01605                if (child is not IItemByName)
 1606                {
 01607                    var childProtocol = childOwner.PathProtocol;
 01608                    if (!childProtocol.HasValue || childProtocol.Value != Model.MediaInfo.MediaProtocol.File)
 1609                    {
 01610                        if (!childOwner.IsVisibleStandalone(user))
 1611                        {
 01612                            continue;
 1613                        }
 1614                    }
 1615                    else
 1616                    {
 01617                        var itemCollectionFolderIds =
 01618                            LibraryManager.GetCollectionFolders(childOwner, allUserRootChildren).Select(f => f.Id);
 1619
 01620                        if (!itemCollectionFolderIds.Any(collectionFolderIds.Contains))
 1621                        {
 1622                            continue;
 1623                        }
 1624                    }
 1625                }
 1626
 01627                list.Add(child);
 1628            }
 1629
 01630            return list;
 1631        }
 1632
 1633        /// <summary>
 1634        /// Gets the linked children.
 1635        /// </summary>
 1636        /// <returns>IEnumerable{BaseItem}.</returns>
 1637        public IReadOnlyList<Tuple<LinkedChild, BaseItem>> GetLinkedChildrenInfos()
 1638        {
 01639            return LinkedChildren
 01640                .Select(i => new Tuple<LinkedChild, BaseItem>(i, GetLinkedChild(i)))
 01641                .Where(i => i.Item2 is not null)
 01642                .ToArray();
 1643        }
 1644
 1645        protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList<FileSystem
 1646        {
 1647            var changesFound = false;
 1648
 1649            if (IsFileProtocol)
 1650            {
 1651                if (RefreshLinkedChildren(fileSystemChildren))
 1652                {
 1653                    changesFound = true;
 1654                }
 1655            }
 1656
 1657            var baseHasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).Configur
 1658
 1659            return baseHasChanges || changesFound;
 1660        }
 1661
 1662        /// <summary>
 1663        /// Refreshes the linked children.
 1664        /// </summary>
 1665        /// <param name="fileSystemChildren">The enumerable of file system metadata.</param>
 1666        /// <returns><c>true</c> if the linked children were updated, <c>false</c> otherwise.</returns>
 1667        protected virtual bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
 1668        {
 201669            if (SupportsShortcutChildren)
 1670            {
 201671                var newShortcutLinks = fileSystemChildren
 201672                    .Where(i => !i.IsDirectory && FileSystem.IsShortcut(i.FullName))
 201673                    .Select(i =>
 201674                    {
 201675                        try
 201676                        {
 201677                            Logger.LogDebug("Found shortcut at {0}", i.FullName);
 201678
 201679                            var resolvedPath = CollectionFolder.ApplicationHost.ExpandVirtualPath(FileSystem.ResolveShor
 201680
 201681                            if (!string.IsNullOrEmpty(resolvedPath))
 201682                            {
 201683                                return new LinkedChild
 201684                                {
 201685                                    Path = resolvedPath,
 201686                                    Type = LinkedChildType.Shortcut
 201687                                };
 201688                            }
 201689
 201690                            Logger.LogError("Error resolving shortcut {0}", i.FullName);
 201691
 201692                            return null;
 201693                        }
 201694                        catch (IOException ex)
 201695                        {
 201696                            Logger.LogError(ex, "Error resolving shortcut {0}", i.FullName);
 201697                            return null;
 201698                        }
 201699                    })
 201700                    .Where(i => i is not null)
 201701                    .ToList();
 1702
 201703                var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
 1704
 201705                if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer(FileSystem)))
 1706                {
 01707                    Logger.LogInformation("Shortcut links have changed for {0}", Path);
 1708
 01709                    newShortcutLinks.AddRange(LinkedChildren.Where(i => i.Type == LinkedChildType.Manual));
 01710                    LinkedChildren = newShortcutLinks.ToArray();
 01711                    return true;
 1712                }
 1713            }
 1714
 401715            foreach (var child in LinkedChildren)
 1716            {
 1717                // Reset the cached value
 01718                child.ItemId = null;
 1719            }
 1720
 201721            return false;
 1722        }
 1723
 1724        /// <summary>
 1725        /// Marks the played.
 1726        /// </summary>
 1727        /// <param name="user">The user.</param>
 1728        /// <param name="datePlayed">The date played.</param>
 1729        /// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
 1730        public override void MarkPlayed(
 1731            User user,
 1732            DateTime? datePlayed,
 1733            bool resetPosition)
 1734        {
 01735            var query = new InternalItemsQuery
 01736            {
 01737                User = user,
 01738                Recursive = true,
 01739                IsFolder = false,
 01740                EnableTotalRecordCount = false
 01741            };
 1742
 01743            if (!user.DisplayMissingEpisodes)
 1744            {
 01745                query.IsVirtualItem = false;
 1746            }
 1747
 01748            var itemsResult = GetItemList(query);
 1749
 1750            // Sweep through recursively and update status
 01751            foreach (var item in itemsResult)
 1752            {
 01753                if (item.IsVirtualItem)
 1754                {
 1755                    // The querying doesn't support virtual unaired
 01756                    var episode = item as Episode;
 01757                    if (episode is not null && episode.IsUnaired)
 1758                    {
 1759                        continue;
 1760                    }
 1761                }
 1762
 01763                item.MarkPlayed(user, datePlayed, resetPosition);
 1764            }
 01765        }
 1766
 1767        /// <summary>
 1768        /// Marks the unplayed.
 1769        /// </summary>
 1770        /// <param name="user">The user.</param>
 1771        public override void MarkUnplayed(User user)
 1772        {
 01773            var itemsResult = GetItemList(new InternalItemsQuery
 01774            {
 01775                User = user,
 01776                Recursive = true,
 01777                IsFolder = false,
 01778                EnableTotalRecordCount = false
 01779            });
 1780
 1781            // Sweep through recursively and update status
 01782            foreach (var item in itemsResult)
 1783            {
 01784                item.MarkUnplayed(user);
 1785            }
 01786        }
 1787
 1788        public override bool IsPlayed(User user, UserItemData userItemData)
 1789        {
 01790            return ItemRepository.GetIsPlayed(user, Id, true);
 1791        }
 1792
 1793        public override bool IsUnplayed(User user, UserItemData userItemData)
 1794        {
 01795            return !IsPlayed(user, userItemData);
 1796        }
 1797
 1798        public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User
 1799        {
 91800            if (!SupportsUserDataFromChildren)
 1801            {
 91802                return;
 1803            }
 1804
 01805            if (itemDto is not null && fields.ContainsField(ItemFields.RecursiveItemCount))
 1806            {
 01807                itemDto.RecursiveItemCount = GetRecursiveChildCount(user);
 1808            }
 1809
 01810            if (SupportsPlayedStatus)
 1811            {
 01812                var unplayedQueryResult = GetItems(new InternalItemsQuery(user)
 01813                {
 01814                    Recursive = true,
 01815                    IsFolder = false,
 01816                    IsVirtualItem = false,
 01817                    EnableTotalRecordCount = true,
 01818                    Limit = 0,
 01819                    IsPlayed = false,
 01820                    DtoOptions = new DtoOptions(false)
 01821                    {
 01822                        EnableImages = false
 01823                    }
 01824                }).TotalRecordCount;
 1825
 01826                dto.UnplayedItemCount = unplayedQueryResult;
 1827
 01828                if (itemDto?.RecursiveItemCount > 0)
 1829                {
 01830                    var unplayedPercentage = ((double)unplayedQueryResult / itemDto.RecursiveItemCount.Value) * 100;
 01831                    dto.PlayedPercentage = 100 - unplayedPercentage;
 01832                    dto.Played = dto.PlayedPercentage.Value >= 100;
 1833                }
 1834                else
 1835                {
 01836                    dto.Played = (dto.UnplayedItemCount ?? 0) == 0;
 1837                }
 1838            }
 01839        }
 1840
 1841        /// <summary>
 1842        /// Contains constants used when reporting scan progress.
 1843        /// </summary>
 1844        private static class ProgressHelpers
 1845        {
 1846            /// <summary>
 1847            /// Reported after the folders immediate children are retrieved.
 1848            /// </summary>
 1849            public const int RetrievedChildren = 5;
 1850
 1851            /// <summary>
 1852            /// Reported after add, updating, or deleting child items from the LibraryManager.
 1853            /// </summary>
 1854            public const int UpdatedChildItems = 10;
 1855
 1856            /// <summary>
 1857            /// Reported once subfolders are scanned.
 1858            /// When scanning subfolders, the progress will be between [UpdatedItems, ScannedSubfolders].
 1859            /// </summary>
 1860            public const int ScannedSubfolders = 50;
 1861
 1862            /// <summary>
 1863            /// Reported once metadata is refreshed.
 1864            /// When refreshing metadata, the progress will be between [ScannedSubfolders, MetadataRefreshed].
 1865            /// </summary>
 1866            public const int RefreshedMetadata = 100;
 1867
 1868            /// <summary>
 1869            /// Gets the current progress given the previous step, next step, and progress in between.
 1870            /// </summary>
 1871            /// <param name="previousProgressStep">The previous progress step.</param>
 1872            /// <param name="nextProgressStep">The next progress step.</param>
 1873            /// <param name="currentProgress">The current progress step.</param>
 1874            /// <returns>The progress.</returns>
 1875            public static double GetProgress(int previousProgressStep, int nextProgressStep, double currentProgress)
 1876            {
 731877                return previousProgressStep + ((nextProgressStep - previousProgressStep) * (currentProgress / 100));
 1878            }
 1879        }
 1880    }
 1881}

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)