< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Library.UserViewManager
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Library/UserViewManager.cs
Line coverage
30%
Covered lines: 66
Uncovered lines: 148
Coverable lines: 214
Total lines: 435
Line coverage: 30.8%
Branch coverage
14%
Covered branches: 16
Total branches: 110
Branch coverage: 14.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/12/2026 - 12:15:57 AM Line coverage: 27.2% (54/198) Branch coverage: 15.6% (16/102) Total lines: 4045/20/2026 - 12:15:44 AM Line coverage: 27.2% (54/198) Branch coverage: 14.7% (15/102) Total lines: 40410/29/2026 - 12:17:11 AM Line coverage: 30.8% (66/214) Branch coverage: 14.5% (16/110) Total lines: 435 5/12/2026 - 12:15:57 AM Line coverage: 27.2% (54/198) Branch coverage: 15.6% (16/102) Total lines: 4045/20/2026 - 12:15:44 AM Line coverage: 27.2% (54/198) Branch coverage: 14.7% (15/102) Total lines: 40410/29/2026 - 12:17:11 AM Line coverage: 30.8% (66/214) Branch coverage: 14.5% (16/110) Total lines: 435

Coverage delta

Coverage delta 4 -4

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetUserViews(...)38.88%603673.43%
HasVisibleItem(...)50%2290.9%
GetTopParentIds(...)25%44100%
GetUserSubViewWithName(...)100%210%
GetUserSubView(...)100%210%
GetUserView(...)0%4260%
GetUserView(...)100%210%
GetLatestItems(...)0%156120%
GetItemsForLatestItems(...)0%2550500%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Library/UserViewManager.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Linq;
 8using System.Threading;
 9using Jellyfin.Data;
 10using Jellyfin.Data.Enums;
 11using Jellyfin.Database.Implementations.Entities;
 12using Jellyfin.Database.Implementations.Enums;
 13using Jellyfin.Extensions;
 14using MediaBrowser.Controller.Channels;
 15using MediaBrowser.Controller.Configuration;
 16using MediaBrowser.Controller.Dto;
 17using MediaBrowser.Controller.Entities;
 18using MediaBrowser.Controller.Library;
 19using MediaBrowser.Controller.LiveTv;
 20using MediaBrowser.Model.Channels;
 21using MediaBrowser.Model.Globalization;
 22using MediaBrowser.Model.Library;
 23using MediaBrowser.Model.Querying;
 24
 25namespace Emby.Server.Implementations.Library
 26{
 27    public class UserViewManager : IUserViewManager
 28    {
 29        private readonly ILibraryManager _libraryManager;
 30        private readonly ILocalizationManager _localizationManager;
 31
 32        private readonly IChannelManager _channelManager;
 33        private readonly ILiveTvManager _liveTvManager;
 34        private readonly IServerConfigurationManager _config;
 35
 36        public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IChannelManager
 37        {
 2738            _libraryManager = libraryManager;
 2739            _localizationManager = localizationManager;
 2740            _channelManager = channelManager;
 2741            _liveTvManager = liveTvManager;
 2742            _config = config;
 2743        }
 44
 45        public Folder[] GetUserViews(UserViewQuery query)
 46        {
 147            var user = query.User;
 48
 149            var folders = _libraryManager.GetUserRootFolder()
 150                .GetChildren(user, true)
 151                .OfType<Folder>()
 152                .ToList();
 53
 154            var groupedFolders = new List<ICollectionFolder>();
 155            var list = new List<Folder>();
 56
 457            foreach (var folder in folders)
 58            {
 159                var collectionFolder = folder as ICollectionFolder;
 160                var folderViewType = collectionFolder?.CollectionType;
 61
 62                // Playlist and BoxSet libraries require special handling because the folder only references linked item
 163                if (folderViewType == CollectionType.playlists || folderViewType == CollectionType.boxsets)
 64                {
 165                    var itemKind = folderViewType == CollectionType.playlists
 166                        ? BaseItemKind.Playlist
 167                        : BaseItemKind.BoxSet;
 68
 169                    if (!HasVisibleItem(itemKind, folders, user))
 70                    {
 71                        continue;
 72                    }
 73                }
 74
 075                if (UserView.IsUserSpecific(folder))
 76                {
 077                    list.Add(_libraryManager.GetNamedView(user, folder.Name, folder.Id, folderViewType, null));
 078                    continue;
 79                }
 80
 081                if (collectionFolder is not null && UserView.IsEligibleForGrouping(folder) && user.IsFolderGrouped(folde
 82                {
 083                    groupedFolders.Add(collectionFolder);
 084                    continue;
 85                }
 86
 087                if (query.PresetViews.Contains(folderViewType))
 88                {
 089                    list.Add(GetUserView(folder, folderViewType, string.Empty));
 90                }
 91                else
 92                {
 093                    list.Add(folder);
 94                }
 95            }
 96
 697            foreach (var viewType in new[] { CollectionType.movies, CollectionType.tvshows })
 98            {
 299                var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null)
 2100                    .ToList();
 101
 2102                if (parents.Count > 0)
 103                {
 0104                    var localizationKey = viewType == CollectionType.tvshows
 0105                        ? "TvShows"
 0106                        : "Movies";
 107
 0108                    list.Add(GetUserView(parents, viewType, localizationKey, string.Empty, user, query.PresetViews));
 109                }
 110            }
 111
 1112            if (_config.Configuration.EnableFolderView)
 113            {
 0114                var name = _localizationManager.GetServerLocalizedString("Folders");
 0115                list.Add(_libraryManager.GetNamedView(name, CollectionType.folders, string.Empty));
 116            }
 117
 1118            if (query.IncludeExternalContent)
 119            {
 1120                var channelResult = _channelManager.GetChannelsInternalAsync(new ChannelQuery
 1121                {
 1122                    UserId = user.Id
 1123                }).GetAwaiter().GetResult();
 124
 1125                var channels = channelResult.Items;
 126
 1127                list.AddRange(channels);
 128
 1129                if (_liveTvManager.IsEnabledForUser(user))
 130                {
 0131                    list.Add(_liveTvManager.GetInternalLiveTvFolder(CancellationToken.None));
 132                }
 133            }
 134
 1135            if (!query.IncludeHidden)
 136            {
 0137                list = list.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes).Contains(i.Id)).T
 138            }
 139
 1140            var sorted = _libraryManager.Sort(list, user, [ItemSortBy.SortName], SortOrder.Ascending).ToList();
 1141            var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews);
 142
 1143            return list
 1144                .OrderBy(i =>
 1145                {
 1146                    var index = Array.IndexOf(orders, i.Id);
 1147                    if (index == -1
 1148                        && i is UserView view
 1149                        && !view.DisplayParentId.IsEmpty())
 1150                    {
 1151                        index = Array.IndexOf(orders, view.DisplayParentId);
 1152                    }
 1153
 1154                    return index == -1 ? int.MaxValue : index;
 1155                })
 1156                .ThenBy(sorted.IndexOf)
 1157                .ThenBy(i => i.SortName)
 1158                .ToArray();
 159        }
 160
 161        private bool HasVisibleItem(BaseItemKind itemKind, IReadOnlyList<Folder> folders, User user)
 162        {
 1163            var topParentIds = folders.SelectMany(GetTopParentIds).ToArray();
 1164            if (topParentIds.Length == 0)
 165            {
 0166                return false;
 167            }
 168
 1169            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 1170            {
 1171                IncludeItemTypes = [itemKind],
 1172                TopParentIds = topParentIds,
 1173                GroupByPresentationUniqueKey = false,
 1174                DtoOptions = DtoOptions.StoredColumnsOnly
 1175            });
 176
 1177            return items.Any(item => item.IsVisible(user));
 178        }
 179
 180        private static IEnumerable<Guid> GetTopParentIds(Folder folder)
 181        {
 1182            return folder is CollectionFolder collectionFolder && collectionFolder.PhysicalFolderIds.Length > 0
 1183                ? collectionFolder.PhysicalFolderIds
 1184                : [folder.Id];
 185        }
 186
 187        public UserView GetUserSubViewWithName(string name, Guid parentId, CollectionType? type, string sortName)
 188        {
 0189            var uniqueId = parentId + "subview" + type;
 190
 0191            return _libraryManager.GetNamedView(name, parentId, type, sortName, uniqueId);
 192        }
 193
 194        public UserView GetUserSubView(Guid parentId, CollectionType? type, string localizationKey, string sortName)
 195        {
 0196            var name = _localizationManager.GetServerLocalizedString(localizationKey);
 197
 0198            return GetUserSubViewWithName(name, parentId, type, sortName);
 199        }
 200
 201        private Folder GetUserView(
 202            List<ICollectionFolder> parents,
 203            CollectionType? viewType,
 204            string localizationKey,
 205            string sortName,
 206            User user,
 207            CollectionType?[] presetViews)
 208        {
 0209            if (parents.Count == 1 && parents.All(i => i.CollectionType == viewType))
 210            {
 0211                if (!presetViews.Contains(viewType))
 212                {
 0213                    return (Folder)parents[0];
 214                }
 215
 0216                return GetUserView((Folder)parents[0], viewType, string.Empty);
 217            }
 218
 0219            var name = _localizationManager.GetServerLocalizedString(localizationKey);
 0220            return _libraryManager.GetNamedView(user, name, viewType, sortName);
 221        }
 222
 223        public UserView GetUserView(Folder parent, CollectionType? viewType, string sortName)
 224        {
 0225            return _libraryManager.GetShadowView(parent, viewType, sortName);
 226        }
 227
 228        public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request, DtoOptions options)
 229        {
 0230            var libraryItems = GetItemsForLatestItems(request.User, request, options);
 231
 0232            var list = new List<Tuple<BaseItem, List<BaseItem>>>();
 0233            var containerIndexMap = new Dictionary<Guid, int>();
 0234            foreach (var item in libraryItems)
 235            {
 236                // Only grab the index container for media
 0237                var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer;
 238
 0239                if (container is null)
 240                {
 0241                    list.Add(new Tuple<BaseItem, List<BaseItem>>(null!, new List<BaseItem> { item }));
 242                }
 0243                else if (containerIndexMap.TryGetValue(container.Id, out var existingIndex))
 244                {
 0245                    list[existingIndex].Item2.Add(item);
 246                }
 247                else
 248                {
 0249                    containerIndexMap[container.Id] = list.Count;
 0250                    list.Add(new Tuple<BaseItem, List<BaseItem>>(container, new List<BaseItem> { item }));
 251                }
 252
 0253                if (list.Count >= request.Limit)
 254                {
 0255                    break;
 256                }
 257            }
 258
 0259            return list;
 260        }
 261
 262        private IReadOnlyList<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
 263        {
 0264            var parentId = request.ParentId;
 265
 0266            var includeItemTypes = request.IncludeItemTypes;
 0267            var limit = request.Limit ?? 10;
 268
 0269            var parents = new List<BaseItem>();
 270
 0271            if (!parentId.IsEmpty())
 272            {
 0273                var parentItem = _libraryManager.GetItemById(parentId);
 0274                if (parentItem is Channel)
 275                {
 0276                    return _channelManager.GetLatestChannelItemsInternal(
 0277                        new InternalItemsQuery(user)
 0278                        {
 0279                            ChannelIds = [parentId],
 0280                            IsPlayed = request.IsPlayed,
 0281                            StartIndex = request.StartIndex,
 0282                            Limit = request.Limit,
 0283                            IncludeItemTypes = request.IncludeItemTypes,
 0284                            EnableTotalRecordCount = false
 0285                        },
 0286                        CancellationToken.None).GetAwaiter().GetResult().Items;
 287                }
 288
 0289                if (parentItem is Folder parent)
 290                {
 0291                    parents.Add(parent);
 292                }
 293            }
 294
 0295            var isPlayed = request.IsPlayed;
 296
 0297            if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.music))
 298            {
 0299                isPlayed = null;
 300            }
 301
 0302            if (parents.Count == 0)
 303            {
 0304                parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
 0305                    .Where(i => i is Folder)
 0306                    .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes)
 0307                        .Contains(i.Id))
 0308                    .ToList();
 309            }
 310
 0311            if (parents.Count == 0)
 312            {
 0313                return Array.Empty<BaseItem>();
 314            }
 315
 0316            if (includeItemTypes.Length == 0)
 317            {
 318                // Handle situations with the grouping setting, e.g. movies showing up in tv, etc.
 319                // Thanks to mixed content libraries included in the UserView
 0320                var hasCollectionType = parents.OfType<UserView>().ToList();
 0321                if (hasCollectionType.Count > 0)
 322                {
 0323                    if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies))
 324                    {
 0325                        includeItemTypes = [BaseItemKind.Movie];
 326                    }
 0327                    else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows))
 328                    {
 0329                        includeItemTypes = [BaseItemKind.Episode];
 330                    }
 331                }
 332            }
 333
 0334            MediaType[] mediaTypes = [];
 335
 0336            if (includeItemTypes.Length == 0)
 337            {
 0338                HashSet<MediaType> tmpMediaTypes = [];
 0339                foreach (var parent in parents.OfType<ICollectionFolder>())
 340                {
 0341                    switch (parent.CollectionType)
 342                    {
 343                        case CollectionType.books:
 0344                            tmpMediaTypes.Add(MediaType.Book);
 0345                            tmpMediaTypes.Add(MediaType.Audio);
 0346                            break;
 347                        case CollectionType.music:
 0348                            tmpMediaTypes.Add(MediaType.Audio);
 0349                            break;
 350                        case CollectionType.photos:
 0351                            tmpMediaTypes.Add(MediaType.Photo);
 0352                            tmpMediaTypes.Add(MediaType.Video);
 0353                            break;
 354                        case CollectionType.homevideos:
 0355                            tmpMediaTypes.Add(MediaType.Photo);
 0356                            tmpMediaTypes.Add(MediaType.Video);
 0357                            break;
 358                        default:
 0359                            tmpMediaTypes.Add(MediaType.Video);
 360                            break;
 361                    }
 362                }
 363
 0364                mediaTypes = tmpMediaTypes.ToArray();
 365            }
 366
 0367            var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Length == 0
 0368                ?
 0369                [
 0370                    BaseItemKind.Person,
 0371                    BaseItemKind.Studio,
 0372                    BaseItemKind.Year,
 0373                    BaseItemKind.MusicGenre,
 0374                    BaseItemKind.Genre
 0375                ]
 0376                : Array.Empty<BaseItemKind>();
 377
 0378            var query = new InternalItemsQuery(user)
 0379            {
 0380                IncludeItemTypes = includeItemTypes,
 0381                OrderBy =
 0382                [
 0383                    (ItemSortBy.DateCreated, SortOrder.Descending),
 0384                    (ItemSortBy.SortName, SortOrder.Descending),
 0385                    (ItemSortBy.ProductionYear, SortOrder.Descending)
 0386                ],
 0387                IsFolder = includeItemTypes.Length == 0 ? false : null,
 0388                ExcludeItemTypes = excludeItemTypes,
 0389                IsVirtualItem = false,
 0390                Limit = limit * 2,
 0391                IsPlayed = isPlayed,
 0392                DtoOptions = options,
 0393                MediaTypes = mediaTypes
 0394            };
 395
 0396            if (request.GroupItems)
 397            {
 0398                var collectionType = parents
 0399                    .Select(parent => parent switch
 0400                    {
 0401                        ICollectionFolder collectionFolder => collectionFolder.CollectionType,
 0402                        UserView userView => userView.CollectionType,
 0403                        _ => null
 0404                    })
 0405                    .FirstOrDefault(type => type is not null);
 406
 0407                if (collectionType == CollectionType.tvshows)
 408                {
 0409                    query.Limit = limit;
 0410                    return _libraryManager.GetLatestItemList(query, parents, CollectionType.tvshows);
 411                }
 412
 0413                if (collectionType == CollectionType.music)
 414                {
 0415                    query.Limit = limit;
 0416                    return _libraryManager.GetLatestItemList(query, parents, CollectionType.music);
 417                }
 418
 0419                if (collectionType == CollectionType.movies)
 420                {
 0421                    query.Limit = limit;
 0422                    return _libraryManager.GetLatestItemList(query, parents, CollectionType.movies);
 423                }
 424
 0425                if (collectionType is null)
 426                {
 0427                    query.Limit = limit;
 0428                    return _libraryManager.GetLatestItemList(query, parents, CollectionType.unknown);
 429                }
 430            }
 431
 0432            return _libraryManager.GetItemList(query, parents);
 433        }
 434    }
 435}