< 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
27%
Covered lines: 54
Uncovered lines: 144
Coverable lines: 198
Total lines: 404
Line coverage: 27.2%
Branch coverage
15%
Covered branches: 16
Total branches: 102
Branch coverage: 15.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 27.8% (54/194) Branch coverage: 15.3% (15/98) Total lines: 4025/4/2026 - 12:15:16 AM Line coverage: 27.2% (54/198) Branch coverage: 15.6% (16/102) Total lines: 404 1/23/2026 - 12:11:06 AM Line coverage: 27.8% (54/194) Branch coverage: 15.3% (15/98) Total lines: 4025/4/2026 - 12:15:16 AM Line coverage: 27.2% (54/198) Branch coverage: 15.6% (16/102) Total lines: 404

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetUserViews(...)44.44%593673.84%
GetUserSubViewWithName(...)100%210%
GetUserSubView(...)100%210%
GetUserView(...)0%4260%
GetUserView(...)100%210%
GetLatestItems(...)0%156120%
GetItemsForLatestItems(...)0%2352480%

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        {
 2138            _libraryManager = libraryManager;
 2139            _localizationManager = localizationManager;
 2140            _channelManager = channelManager;
 2141            _liveTvManager = liveTvManager;
 2142            _config = config;
 2143        }
 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 items = folder.GetItemList(new InternalItemsQuery(user)
 166                    {
 167                        ParentId = folder.ParentId
 168                    });
 69
 170                    if (!items.Any(item => item.IsVisible(user)))
 71                    {
 72                        continue;
 73                    }
 74                }
 75
 076                if (UserView.IsUserSpecific(folder))
 77                {
 078                    list.Add(_libraryManager.GetNamedView(user, folder.Name, folder.Id, folderViewType, null));
 079                    continue;
 80                }
 81
 082                if (collectionFolder is not null && UserView.IsEligibleForGrouping(folder) && user.IsFolderGrouped(folde
 83                {
 084                    groupedFolders.Add(collectionFolder);
 085                    continue;
 86                }
 87
 088                if (query.PresetViews.Contains(folderViewType))
 89                {
 090                    list.Add(GetUserView(folder, folderViewType, string.Empty));
 91                }
 92                else
 93                {
 094                    list.Add(folder);
 95                }
 96            }
 97
 698            foreach (var viewType in new[] { CollectionType.movies, CollectionType.tvshows })
 99            {
 2100                var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null)
 2101                    .ToList();
 102
 2103                if (parents.Count > 0)
 104                {
 0105                    var localizationKey = viewType == CollectionType.tvshows
 0106                        ? "TvShows"
 0107                        : "Movies";
 108
 0109                    list.Add(GetUserView(parents, viewType, localizationKey, string.Empty, user, query.PresetViews));
 110                }
 111            }
 112
 1113            if (_config.Configuration.EnableFolderView)
 114            {
 0115                var name = _localizationManager.GetLocalizedString("Folders");
 0116                list.Add(_libraryManager.GetNamedView(name, CollectionType.folders, string.Empty));
 117            }
 118
 1119            if (query.IncludeExternalContent)
 120            {
 1121                var channelResult = _channelManager.GetChannelsInternalAsync(new ChannelQuery
 1122                {
 1123                    UserId = user.Id
 1124                }).GetAwaiter().GetResult();
 125
 1126                var channels = channelResult.Items;
 127
 1128                list.AddRange(channels);
 129
 1130                if (_liveTvManager.GetEnabledUsers().Select(i => i.Id).Contains(user.Id))
 131                {
 0132                    list.Add(_liveTvManager.GetInternalLiveTvFolder(CancellationToken.None));
 133                }
 134            }
 135
 1136            if (!query.IncludeHidden)
 137            {
 0138                list = list.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes).Contains(i.Id)).T
 139            }
 140
 1141            var sorted = _libraryManager.Sort(list, user, [ItemSortBy.SortName], SortOrder.Ascending).ToList();
 1142            var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews);
 143
 1144            return list
 1145                .OrderBy(i =>
 1146                {
 1147                    var index = Array.IndexOf(orders, i.Id);
 1148                    if (index == -1
 1149                        && i is UserView view
 1150                        && !view.DisplayParentId.IsEmpty())
 1151                    {
 1152                        index = Array.IndexOf(orders, view.DisplayParentId);
 1153                    }
 1154
 1155                    return index == -1 ? int.MaxValue : index;
 1156                })
 1157                .ThenBy(sorted.IndexOf)
 1158                .ThenBy(i => i.SortName)
 1159                .ToArray();
 160        }
 161
 162        public UserView GetUserSubViewWithName(string name, Guid parentId, CollectionType? type, string sortName)
 163        {
 0164            var uniqueId = parentId + "subview" + type;
 165
 0166            return _libraryManager.GetNamedView(name, parentId, type, sortName, uniqueId);
 167        }
 168
 169        public UserView GetUserSubView(Guid parentId, CollectionType? type, string localizationKey, string sortName)
 170        {
 0171            var name = _localizationManager.GetLocalizedString(localizationKey);
 172
 0173            return GetUserSubViewWithName(name, parentId, type, sortName);
 174        }
 175
 176        private Folder GetUserView(
 177            List<ICollectionFolder> parents,
 178            CollectionType? viewType,
 179            string localizationKey,
 180            string sortName,
 181            User user,
 182            CollectionType?[] presetViews)
 183        {
 0184            if (parents.Count == 1 && parents.All(i => i.CollectionType == viewType))
 185            {
 0186                if (!presetViews.Contains(viewType))
 187                {
 0188                    return (Folder)parents[0];
 189                }
 190
 0191                return GetUserView((Folder)parents[0], viewType, string.Empty);
 192            }
 193
 0194            var name = _localizationManager.GetLocalizedString(localizationKey);
 0195            return _libraryManager.GetNamedView(user, name, viewType, sortName);
 196        }
 197
 198        public UserView GetUserView(Folder parent, CollectionType? viewType, string sortName)
 199        {
 0200            return _libraryManager.GetShadowView(parent, viewType, sortName);
 201        }
 202
 203        public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request, DtoOptions options)
 204        {
 0205            var libraryItems = GetItemsForLatestItems(request.User, request, options);
 206
 0207            var list = new List<Tuple<BaseItem, List<BaseItem>>>();
 0208            var containerIndexMap = new Dictionary<Guid, int>();
 0209            foreach (var item in libraryItems)
 210            {
 211                // Only grab the index container for media
 0212                var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer;
 213
 0214                if (container is null)
 215                {
 0216                    list.Add(new Tuple<BaseItem, List<BaseItem>>(null!, new List<BaseItem> { item }));
 217                }
 0218                else if (containerIndexMap.TryGetValue(container.Id, out var existingIndex))
 219                {
 0220                    list[existingIndex].Item2.Add(item);
 221                }
 222                else
 223                {
 0224                    containerIndexMap[container.Id] = list.Count;
 0225                    list.Add(new Tuple<BaseItem, List<BaseItem>>(container, new List<BaseItem> { item }));
 226                }
 227
 0228                if (list.Count >= request.Limit)
 229                {
 0230                    break;
 231                }
 232            }
 233
 0234            return list;
 235        }
 236
 237        private IReadOnlyList<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
 238        {
 0239            var parentId = request.ParentId;
 240
 0241            var includeItemTypes = request.IncludeItemTypes;
 0242            var limit = request.Limit ?? 10;
 243
 0244            var parents = new List<BaseItem>();
 245
 0246            if (!parentId.IsEmpty())
 247            {
 0248                var parentItem = _libraryManager.GetItemById(parentId);
 0249                if (parentItem is Channel)
 250                {
 0251                    return _channelManager.GetLatestChannelItemsInternal(
 0252                        new InternalItemsQuery(user)
 0253                        {
 0254                            ChannelIds = [parentId],
 0255                            IsPlayed = request.IsPlayed,
 0256                            StartIndex = request.StartIndex,
 0257                            Limit = request.Limit,
 0258                            IncludeItemTypes = request.IncludeItemTypes,
 0259                            EnableTotalRecordCount = false
 0260                        },
 0261                        CancellationToken.None).GetAwaiter().GetResult().Items;
 262                }
 263
 0264                if (parentItem is Folder parent)
 265                {
 0266                    parents.Add(parent);
 267                }
 268            }
 269
 0270            var isPlayed = request.IsPlayed;
 271
 0272            if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.music))
 273            {
 0274                isPlayed = null;
 275            }
 276
 0277            if (parents.Count == 0)
 278            {
 0279                parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
 0280                    .Where(i => i is Folder)
 0281                    .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes)
 0282                        .Contains(i.Id))
 0283                    .ToList();
 284            }
 285
 0286            if (parents.Count == 0)
 287            {
 0288                return Array.Empty<BaseItem>();
 289            }
 290
 0291            if (includeItemTypes.Length == 0)
 292            {
 293                // Handle situations with the grouping setting, e.g. movies showing up in tv, etc.
 294                // Thanks to mixed content libraries included in the UserView
 0295                var hasCollectionType = parents.OfType<UserView>().ToList();
 0296                if (hasCollectionType.Count > 0)
 297                {
 0298                    if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies))
 299                    {
 0300                        includeItemTypes = [BaseItemKind.Movie];
 301                    }
 0302                    else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows))
 303                    {
 0304                        includeItemTypes = [BaseItemKind.Episode];
 305                    }
 306                }
 307            }
 308
 0309            MediaType[] mediaTypes = [];
 310
 0311            if (includeItemTypes.Length == 0)
 312            {
 0313                HashSet<MediaType> tmpMediaTypes = [];
 0314                foreach (var parent in parents.OfType<ICollectionFolder>())
 315                {
 0316                    switch (parent.CollectionType)
 317                    {
 318                        case CollectionType.books:
 0319                            tmpMediaTypes.Add(MediaType.Book);
 0320                            tmpMediaTypes.Add(MediaType.Audio);
 0321                            break;
 322                        case CollectionType.music:
 0323                            tmpMediaTypes.Add(MediaType.Audio);
 0324                            break;
 325                        case CollectionType.photos:
 0326                            tmpMediaTypes.Add(MediaType.Photo);
 0327                            tmpMediaTypes.Add(MediaType.Video);
 0328                            break;
 329                        case CollectionType.homevideos:
 0330                            tmpMediaTypes.Add(MediaType.Photo);
 0331                            tmpMediaTypes.Add(MediaType.Video);
 0332                            break;
 333                        default:
 0334                            tmpMediaTypes.Add(MediaType.Video);
 335                            break;
 336                    }
 337                }
 338
 0339                mediaTypes = tmpMediaTypes.ToArray();
 340            }
 341
 0342            var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Length == 0
 0343                ?
 0344                [
 0345                    BaseItemKind.Person,
 0346                    BaseItemKind.Studio,
 0347                    BaseItemKind.Year,
 0348                    BaseItemKind.MusicGenre,
 0349                    BaseItemKind.Genre
 0350                ]
 0351                : Array.Empty<BaseItemKind>();
 352
 0353            var query = new InternalItemsQuery(user)
 0354            {
 0355                IncludeItemTypes = includeItemTypes,
 0356                OrderBy =
 0357                [
 0358                    (ItemSortBy.DateCreated, SortOrder.Descending),
 0359                    (ItemSortBy.SortName, SortOrder.Descending),
 0360                    (ItemSortBy.ProductionYear, SortOrder.Descending)
 0361                ],
 0362                IsFolder = includeItemTypes.Length == 0 ? false : null,
 0363                ExcludeItemTypes = excludeItemTypes,
 0364                IsVirtualItem = false,
 0365                Limit = limit * 2,
 0366                IsPlayed = isPlayed,
 0367                DtoOptions = options,
 0368                MediaTypes = mediaTypes
 0369            };
 370
 0371            if (request.GroupItems)
 372            {
 0373                var collectionType = parents
 0374                    .Select(parent => parent switch
 0375                    {
 0376                        ICollectionFolder collectionFolder => collectionFolder.CollectionType,
 0377                        UserView userView => userView.CollectionType,
 0378                        _ => null
 0379                    })
 0380                    .FirstOrDefault(type => type is not null);
 381
 0382                if (collectionType == CollectionType.tvshows)
 383                {
 0384                    query.Limit = limit;
 0385                    return _libraryManager.GetLatestItemList(query, parents, CollectionType.tvshows);
 386                }
 387
 0388                if (collectionType == CollectionType.music)
 389                {
 0390                    query.Limit = limit;
 0391                    return _libraryManager.GetLatestItemList(query, parents, CollectionType.music);
 392                }
 393
 0394                if (collectionType == CollectionType.movies)
 395                {
 0396                    query.Limit = limit;
 0397                    return _libraryManager.GetLatestItemList(query, parents, CollectionType.movies);
 398                }
 399            }
 400
 0401            return _libraryManager.GetItemList(query, parents);
 402        }
 403    }
 404}