using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MoreLinq; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Library { public class UserViewManager : IUserViewManager { private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localizationManager; private readonly IUserManager _userManager; private readonly IChannelManager _channelManager; private readonly ILiveTvManager _liveTvManager; private readonly IPlaylistManager _playlists; private readonly ICollectionManager _collectionManager; private readonly IServerConfigurationManager _config; public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IUserManager userManager, IChannelManager channelManager, ILiveTvManager liveTvManager, IPlaylistManager playlists, ICollectionManager collectionManager, IServerConfigurationManager config) { _libraryManager = libraryManager; _localizationManager = localizationManager; _userManager = userManager; _channelManager = channelManager; _liveTvManager = liveTvManager; _playlists = playlists; _collectionManager = collectionManager; _config = config; } public async Task> GetUserViews(UserViewQuery query, CancellationToken cancellationToken) { var user = _userManager.GetUserById(query.UserId); var folders = user.RootFolder .GetChildren(user, true) .OfType() .ToList(); var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList(); var plainFolderIds = user.Configuration.PlainFolderViews.Select(i => new Guid(i)).ToList(); var standaloneFolders = folders .Where(i => UserView.IsExcludedFromGrouping(i) || excludeFolderIds.Contains(i.Id)) .ToList(); var foldersWithViewTypes = folders .Except(standaloneFolders) .OfType() .ToList(); var list = new List(); if (_config.Configuration.EnableUserSpecificUserViews) { foreach (var folder in standaloneFolders) { var collectionFolder = folder as ICollectionFolder; var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType; if (plainFolderIds.Contains(folder.Id)) { list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, false, string.Empty, user, cancellationToken).ConfigureAwait(false)); } else if (!string.IsNullOrWhiteSpace(folderViewType)) { list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, true, string.Empty, user, cancellationToken).ConfigureAwait(false)); } else { list.Add(folder); } } } else { list.AddRange(standaloneFolders); } var parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType)) .ToList(); if (parents.Count > 0) { list.Add(await GetUserView(parents, CollectionType.TvShows, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) || string.Equals(i.CollectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType)) .ToList(); if (parents.Count > 0) { list.Add(await GetUserView(parents, CollectionType.Music, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType)) .ToList(); if (parents.Count > 0) { list.Add(await GetUserView(parents, CollectionType.Movies, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Games, StringComparison.OrdinalIgnoreCase)) .ToList(); if (parents.Count > 0) { list.Add(await GetUserView(parents, CollectionType.Games, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) .ToList(); if (parents.Count > 0) { list.Add(await GetUserView(parents, CollectionType.BoxSets, string.Empty, user, cancellationToken).ConfigureAwait(false)); } parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) .ToList(); if (parents.Count > 0) { list.Add(await GetUserView(parents, CollectionType.Playlists, string.Empty, user, cancellationToken).ConfigureAwait(false)); } if (user.Configuration.DisplayFoldersView) { list.Add(await GetUserView(new List(), CollectionType.Folders, "zz_" + CollectionType.Folders, user, cancellationToken).ConfigureAwait(false)); } if (query.IncludeExternalContent) { var channelResult = await _channelManager.GetChannels(new ChannelQuery { UserId = query.UserId }, cancellationToken).ConfigureAwait(false); var channels = channelResult.Items; var embeddedChannels = channels .Where(i => user.Configuration.DisplayChannelsWithinViews.Contains(i.Id)) .ToList(); list.AddRange(embeddedChannels.Select(i => _channelManager.GetChannel(i.Id))); if (channels.Length > embeddedChannels.Count) { list.Add(await _channelManager.GetInternalChannelFolder(query.UserId, cancellationToken).ConfigureAwait(false)); } if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId)) { //list.Add(await _liveTvManager.GetInternalLiveTvFolder(query.UserId, cancellationToken).ConfigureAwait(false)); list.Add(await GetUserView(new List(), CollectionType.LiveTv, string.Empty, user, cancellationToken).ConfigureAwait(false)); } } var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); var orders = user.Configuration.OrderedViews.ToList(); return list .OrderBy(i => { var index = orders.IndexOf(i.Id.ToString("N")); return index == -1 ? int.MaxValue : index; }) .ThenBy(sorted.IndexOf) .ThenBy(i => i.SortName); } public Task GetUserSubView(string name, string parentId, string type, User user, string sortName, CancellationToken cancellationToken) { return _libraryManager.GetNamedView(user, name, parentId, type, sortName, cancellationToken); } public Task GetUserSubView(string parentId, string type, User user, string sortName, CancellationToken cancellationToken) { var name = _localizationManager.GetLocalizedString("ViewType" + type); return GetUserSubView(name, parentId, type, user, sortName, cancellationToken); } public async Task GetUserView(List parents, string viewType, string sortName, User user, CancellationToken cancellationToken) { if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase))) { var name = parents[0].Name; var parentId = parents[0].Id; var enableRichView = !user.Configuration.PlainFolderViews.Contains(parentId.ToString("N"), StringComparer.OrdinalIgnoreCase); if (_config.Configuration.EnableUserSpecificUserViews || !enableRichView) { viewType = enableRichView ? viewType : null; var view = await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false); if (view.ParentId != parentId) { view.ParentId = parentId; await view.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); } return view; } return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false); } else { var name = _localizationManager.GetLocalizedString("ViewType" + viewType); return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false); } } public Task GetUserView(Guid parentId, string name, string viewType, bool enableRichView, string sortName, User user, CancellationToken cancellationToken) { return _libraryManager.GetNamedView(user, name, parentId.ToString("N"), viewType, sortName, cancellationToken); } public List>> GetLatestItems(LatestItemsQuery request) { var user = _userManager.GetUserById(request.UserId); var includeTypes = request.IncludeItemTypes; var currentUser = user; Func filter = i => { if (includeTypes.Length > 0) { if (!includeTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase)) { return false; } } if (request.IsPlayed.HasValue) { var val = request.IsPlayed.Value; if (i is Video && i.IsPlayed(currentUser) != val) { return false; } } return i.LocationType != LocationType.Virtual && !i.IsFolder; }; // Avoid implicitly captured closure var libraryItems = string.IsNullOrEmpty(request.ParentId) && user != null ? GetItemsConfiguredForLatest(user, filter) : GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId, filter); libraryItems = libraryItems.OrderByDescending(i => i.DateCreated); if (request.IsPlayed.HasValue) { var takeLimit = (request.Limit ?? 20) * 20; libraryItems = libraryItems.Take(takeLimit); } // Avoid implicitly captured closure var items = libraryItems .ToList(); var list = new List>>(); foreach (var item in items) { // Only grab the index container for media var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer; if (container == null) { list.Add(new Tuple>(null, new List { item })); } else { var current = list.FirstOrDefault(i => i.Item1 != null && i.Item1.Id == container.Id); if (current != null) { current.Item2.Add(item); } else { list.Add(new Tuple>(container, new List { item })); } } if (list.Count >= request.Limit) { break; } } return list; } protected IList GetAllLibraryItems(string userId, IUserManager userManager, ILibraryManager libraryManager, string parentId, Func filter) { if (!string.IsNullOrEmpty(parentId)) { var folder = (Folder)libraryManager.GetItemById(new Guid(parentId)); if (!string.IsNullOrWhiteSpace(userId)) { var user = userManager.GetUserById(userId); if (user == null) { throw new ArgumentException("User not found"); } return folder .GetRecursiveChildren(user, filter) .ToList(); } return folder .GetRecursiveChildren(filter); } if (!string.IsNullOrWhiteSpace(userId)) { var user = userManager.GetUserById(userId); if (user == null) { throw new ArgumentException("User not found"); } return user .RootFolder .GetRecursiveChildren(user, filter) .ToList(); } return libraryManager .RootFolder .GetRecursiveChildren(filter); } private IEnumerable GetItemsConfiguredForLatest(User user, Func filter) { // Avoid implicitly captured closure var currentUser = user; return user.RootFolder.GetChildren(user, true) .OfType() .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N"))) .SelectMany(i => i.GetRecursiveChildren(currentUser, filter)) .DistinctBy(i => i.Id); } } }