| | 1 | | #nullable disable |
| | 2 | |
|
| | 3 | | #pragma warning disable CS1591 |
| | 4 | |
|
| | 5 | | using System; |
| | 6 | | using System.Collections.Generic; |
| | 7 | | using System.Linq; |
| | 8 | | using System.Threading; |
| | 9 | | using Jellyfin.Data.Entities; |
| | 10 | | using Jellyfin.Data.Enums; |
| | 11 | | using Jellyfin.Extensions; |
| | 12 | | using MediaBrowser.Controller.Channels; |
| | 13 | | using MediaBrowser.Controller.Configuration; |
| | 14 | | using MediaBrowser.Controller.Dto; |
| | 15 | | using MediaBrowser.Controller.Entities; |
| | 16 | | using MediaBrowser.Controller.Library; |
| | 17 | | using MediaBrowser.Controller.LiveTv; |
| | 18 | | using MediaBrowser.Model.Channels; |
| | 19 | | using MediaBrowser.Model.Globalization; |
| | 20 | | using MediaBrowser.Model.Library; |
| | 21 | | using MediaBrowser.Model.Querying; |
| | 22 | |
|
| | 23 | | namespace Emby.Server.Implementations.Library |
| | 24 | | { |
| | 25 | | public class UserViewManager : IUserViewManager |
| | 26 | | { |
| | 27 | | private readonly ILibraryManager _libraryManager; |
| | 28 | | private readonly ILocalizationManager _localizationManager; |
| | 29 | |
|
| | 30 | | private readonly IChannelManager _channelManager; |
| | 31 | | private readonly ILiveTvManager _liveTvManager; |
| | 32 | | private readonly IServerConfigurationManager _config; |
| | 33 | |
|
| | 34 | | public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IChannelManager |
| | 35 | | { |
| 22 | 36 | | _libraryManager = libraryManager; |
| 22 | 37 | | _localizationManager = localizationManager; |
| 22 | 38 | | _channelManager = channelManager; |
| 22 | 39 | | _liveTvManager = liveTvManager; |
| 22 | 40 | | _config = config; |
| 22 | 41 | | } |
| | 42 | |
|
| | 43 | | public Folder[] GetUserViews(UserViewQuery query) |
| | 44 | | { |
| 1 | 45 | | var user = query.User; |
| | 46 | |
|
| 1 | 47 | | var folders = _libraryManager.GetUserRootFolder() |
| 1 | 48 | | .GetChildren(user, true) |
| 1 | 49 | | .OfType<Folder>() |
| 1 | 50 | | .ToList(); |
| | 51 | |
|
| 1 | 52 | | var groupedFolders = new List<ICollectionFolder>(); |
| 1 | 53 | | var list = new List<Folder>(); |
| | 54 | |
|
| 4 | 55 | | foreach (var folder in folders) |
| | 56 | | { |
| 1 | 57 | | var collectionFolder = folder as ICollectionFolder; |
| 1 | 58 | | var folderViewType = collectionFolder?.CollectionType; |
| | 59 | |
|
| | 60 | | // Playlist library requires special handling because the folder only references user playlists |
| 1 | 61 | | if (folderViewType == CollectionType.playlists) |
| | 62 | | { |
| 1 | 63 | | var items = folder.GetItemList(new InternalItemsQuery(user) |
| 1 | 64 | | { |
| 1 | 65 | | ParentId = folder.ParentId |
| 1 | 66 | | }); |
| | 67 | |
|
| 1 | 68 | | if (!items.Any(item => item.IsVisible(user))) |
| | 69 | | { |
| | 70 | | continue; |
| | 71 | | } |
| | 72 | | } |
| | 73 | |
|
| 0 | 74 | | if (UserView.IsUserSpecific(folder)) |
| | 75 | | { |
| 0 | 76 | | list.Add(_libraryManager.GetNamedView(user, folder.Name, folder.Id, folderViewType, null)); |
| 0 | 77 | | continue; |
| | 78 | | } |
| | 79 | |
|
| 0 | 80 | | if (collectionFolder is not null && UserView.IsEligibleForGrouping(folder) && user.IsFolderGrouped(folde |
| | 81 | | { |
| 0 | 82 | | groupedFolders.Add(collectionFolder); |
| 0 | 83 | | continue; |
| | 84 | | } |
| | 85 | |
|
| 0 | 86 | | if (query.PresetViews.Contains(folderViewType)) |
| | 87 | | { |
| 0 | 88 | | list.Add(GetUserView(folder, folderViewType, string.Empty)); |
| | 89 | | } |
| | 90 | | else |
| | 91 | | { |
| 0 | 92 | | list.Add(folder); |
| | 93 | | } |
| | 94 | | } |
| | 95 | |
|
| 6 | 96 | | foreach (var viewType in new[] { CollectionType.movies, CollectionType.tvshows }) |
| | 97 | | { |
| 2 | 98 | | var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null) |
| 2 | 99 | | .ToList(); |
| | 100 | |
|
| 2 | 101 | | if (parents.Count > 0) |
| | 102 | | { |
| 0 | 103 | | var localizationKey = viewType == CollectionType.tvshows |
| 0 | 104 | | ? "TvShows" |
| 0 | 105 | | : "Movies"; |
| | 106 | |
|
| 0 | 107 | | list.Add(GetUserView(parents, viewType, localizationKey, string.Empty, user, query.PresetViews)); |
| | 108 | | } |
| | 109 | | } |
| | 110 | |
|
| 1 | 111 | | if (_config.Configuration.EnableFolderView) |
| | 112 | | { |
| 0 | 113 | | var name = _localizationManager.GetLocalizedString("Folders"); |
| 0 | 114 | | list.Add(_libraryManager.GetNamedView(name, CollectionType.folders, string.Empty)); |
| | 115 | | } |
| | 116 | |
|
| 1 | 117 | | if (query.IncludeExternalContent) |
| | 118 | | { |
| 1 | 119 | | var channelResult = _channelManager.GetChannelsInternalAsync(new ChannelQuery |
| 1 | 120 | | { |
| 1 | 121 | | UserId = user.Id |
| 1 | 122 | | }).GetAwaiter().GetResult(); |
| | 123 | |
|
| 1 | 124 | | var channels = channelResult.Items; |
| | 125 | |
|
| 1 | 126 | | list.AddRange(channels); |
| | 127 | |
|
| 1 | 128 | | if (_liveTvManager.GetEnabledUsers().Select(i => i.Id).Contains(user.Id)) |
| | 129 | | { |
| 0 | 130 | | list.Add(_liveTvManager.GetInternalLiveTvFolder(CancellationToken.None)); |
| | 131 | | } |
| | 132 | | } |
| | 133 | |
|
| 1 | 134 | | if (!query.IncludeHidden) |
| | 135 | | { |
| 0 | 136 | | list = list.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes).Contains(i.Id)).T |
| | 137 | | } |
| | 138 | |
|
| 1 | 139 | | var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); |
| 1 | 140 | | var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews); |
| | 141 | |
|
| 1 | 142 | | return list |
| 1 | 143 | | .OrderBy(i => |
| 1 | 144 | | { |
| 1 | 145 | | var index = Array.IndexOf(orders, i.Id); |
| 1 | 146 | | if (index == -1 |
| 1 | 147 | | && i is UserView view |
| 1 | 148 | | && !view.DisplayParentId.IsEmpty()) |
| 1 | 149 | | { |
| 1 | 150 | | index = Array.IndexOf(orders, view.DisplayParentId); |
| 1 | 151 | | } |
| 1 | 152 | |
|
| 1 | 153 | | return index == -1 ? int.MaxValue : index; |
| 1 | 154 | | }) |
| 1 | 155 | | .ThenBy(sorted.IndexOf) |
| 1 | 156 | | .ThenBy(i => i.SortName) |
| 1 | 157 | | .ToArray(); |
| | 158 | | } |
| | 159 | |
|
| | 160 | | public UserView GetUserSubViewWithName(string name, Guid parentId, CollectionType? type, string sortName) |
| | 161 | | { |
| 0 | 162 | | var uniqueId = parentId + "subview" + type; |
| | 163 | |
|
| 0 | 164 | | return _libraryManager.GetNamedView(name, parentId, type, sortName, uniqueId); |
| | 165 | | } |
| | 166 | |
|
| | 167 | | public UserView GetUserSubView(Guid parentId, CollectionType? type, string localizationKey, string sortName) |
| | 168 | | { |
| 0 | 169 | | var name = _localizationManager.GetLocalizedString(localizationKey); |
| | 170 | |
|
| 0 | 171 | | return GetUserSubViewWithName(name, parentId, type, sortName); |
| | 172 | | } |
| | 173 | |
|
| | 174 | | private Folder GetUserView( |
| | 175 | | List<ICollectionFolder> parents, |
| | 176 | | CollectionType? viewType, |
| | 177 | | string localizationKey, |
| | 178 | | string sortName, |
| | 179 | | User user, |
| | 180 | | CollectionType?[] presetViews) |
| | 181 | | { |
| 0 | 182 | | if (parents.Count == 1 && parents.All(i => i.CollectionType == viewType)) |
| | 183 | | { |
| 0 | 184 | | if (!presetViews.Contains(viewType)) |
| | 185 | | { |
| 0 | 186 | | return (Folder)parents[0]; |
| | 187 | | } |
| | 188 | |
|
| 0 | 189 | | return GetUserView((Folder)parents[0], viewType, string.Empty); |
| | 190 | | } |
| | 191 | |
|
| 0 | 192 | | var name = _localizationManager.GetLocalizedString(localizationKey); |
| 0 | 193 | | return _libraryManager.GetNamedView(user, name, viewType, sortName); |
| | 194 | | } |
| | 195 | |
|
| | 196 | | public UserView GetUserView(Folder parent, CollectionType? viewType, string sortName) |
| | 197 | | { |
| 0 | 198 | | return _libraryManager.GetShadowView(parent, viewType, sortName); |
| | 199 | | } |
| | 200 | |
|
| | 201 | | public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request, DtoOptions options) |
| | 202 | | { |
| 0 | 203 | | var libraryItems = GetItemsForLatestItems(request.User, request, options); |
| | 204 | |
|
| 0 | 205 | | var list = new List<Tuple<BaseItem, List<BaseItem>>>(); |
| | 206 | |
|
| 0 | 207 | | foreach (var item in libraryItems) |
| | 208 | | { |
| | 209 | | // Only grab the index container for media |
| 0 | 210 | | var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer; |
| | 211 | |
|
| 0 | 212 | | if (container is null) |
| | 213 | | { |
| 0 | 214 | | list.Add(new Tuple<BaseItem, List<BaseItem>>(null, new List<BaseItem> { item })); |
| | 215 | | } |
| | 216 | | else |
| | 217 | | { |
| 0 | 218 | | var current = list.FirstOrDefault(i => i.Item1 is not null && i.Item1.Id.Equals(container.Id)); |
| | 219 | |
|
| 0 | 220 | | if (current is not null) |
| | 221 | | { |
| 0 | 222 | | current.Item2.Add(item); |
| | 223 | | } |
| | 224 | | else |
| | 225 | | { |
| 0 | 226 | | list.Add(new Tuple<BaseItem, List<BaseItem>>(container, new List<BaseItem> { item })); |
| | 227 | | } |
| | 228 | | } |
| | 229 | |
|
| 0 | 230 | | if (list.Count >= request.Limit) |
| | 231 | | { |
| 0 | 232 | | break; |
| | 233 | | } |
| | 234 | | } |
| | 235 | |
|
| 0 | 236 | | return list; |
| | 237 | | } |
| | 238 | |
|
| | 239 | | private IReadOnlyList<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options) |
| | 240 | | { |
| 0 | 241 | | var parentId = request.ParentId; |
| | 242 | |
|
| 0 | 243 | | var includeItemTypes = request.IncludeItemTypes; |
| 0 | 244 | | var limit = request.Limit ?? 10; |
| | 245 | |
|
| 0 | 246 | | var parents = new List<BaseItem>(); |
| | 247 | |
|
| 0 | 248 | | if (!parentId.IsEmpty()) |
| | 249 | | { |
| 0 | 250 | | var parentItem = _libraryManager.GetItemById(parentId); |
| 0 | 251 | | if (parentItem is Channel) |
| | 252 | | { |
| 0 | 253 | | return _channelManager.GetLatestChannelItemsInternal( |
| 0 | 254 | | new InternalItemsQuery(user) |
| 0 | 255 | | { |
| 0 | 256 | | ChannelIds = new[] { parentId }, |
| 0 | 257 | | IsPlayed = request.IsPlayed, |
| 0 | 258 | | StartIndex = request.StartIndex, |
| 0 | 259 | | Limit = request.Limit, |
| 0 | 260 | | IncludeItemTypes = request.IncludeItemTypes, |
| 0 | 261 | | EnableTotalRecordCount = false |
| 0 | 262 | | }, |
| 0 | 263 | | CancellationToken.None).GetAwaiter().GetResult().Items; |
| | 264 | | } |
| | 265 | |
|
| 0 | 266 | | if (parentItem is Folder parent) |
| | 267 | | { |
| 0 | 268 | | parents.Add(parent); |
| | 269 | | } |
| | 270 | | } |
| | 271 | |
|
| 0 | 272 | | var isPlayed = request.IsPlayed; |
| | 273 | |
|
| 0 | 274 | | if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.music)) |
| | 275 | | { |
| 0 | 276 | | isPlayed = null; |
| | 277 | | } |
| | 278 | |
|
| 0 | 279 | | if (parents.Count == 0) |
| | 280 | | { |
| 0 | 281 | | parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) |
| 0 | 282 | | .Where(i => i is Folder) |
| 0 | 283 | | .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes) |
| 0 | 284 | | .Contains(i.Id)) |
| 0 | 285 | | .ToList(); |
| | 286 | | } |
| | 287 | |
|
| 0 | 288 | | if (parents.Count == 0) |
| | 289 | | { |
| 0 | 290 | | return Array.Empty<BaseItem>(); |
| | 291 | | } |
| | 292 | |
|
| 0 | 293 | | if (includeItemTypes.Length == 0) |
| | 294 | | { |
| | 295 | | // Handle situations with the grouping setting, e.g. movies showing up in tv, etc. |
| | 296 | | // Thanks to mixed content libraries included in the UserView |
| 0 | 297 | | var hasCollectionType = parents.OfType<UserView>().ToList(); |
| 0 | 298 | | if (hasCollectionType.Count > 0) |
| | 299 | | { |
| 0 | 300 | | if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies)) |
| | 301 | | { |
| 0 | 302 | | includeItemTypes = new[] { BaseItemKind.Movie }; |
| | 303 | | } |
| 0 | 304 | | else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows)) |
| | 305 | | { |
| 0 | 306 | | includeItemTypes = new[] { BaseItemKind.Episode }; |
| | 307 | | } |
| | 308 | | } |
| | 309 | | } |
| | 310 | |
|
| 0 | 311 | | var mediaTypes = new List<MediaType>(); |
| | 312 | |
|
| 0 | 313 | | if (includeItemTypes.Length == 0) |
| | 314 | | { |
| 0 | 315 | | foreach (var parent in parents.OfType<ICollectionFolder>()) |
| | 316 | | { |
| 0 | 317 | | switch (parent.CollectionType) |
| | 318 | | { |
| | 319 | | case CollectionType.books: |
| 0 | 320 | | mediaTypes.Add(MediaType.Book); |
| 0 | 321 | | mediaTypes.Add(MediaType.Audio); |
| 0 | 322 | | break; |
| | 323 | | case CollectionType.music: |
| 0 | 324 | | mediaTypes.Add(MediaType.Audio); |
| 0 | 325 | | break; |
| | 326 | | case CollectionType.photos: |
| 0 | 327 | | mediaTypes.Add(MediaType.Photo); |
| 0 | 328 | | mediaTypes.Add(MediaType.Video); |
| 0 | 329 | | break; |
| | 330 | | case CollectionType.homevideos: |
| 0 | 331 | | mediaTypes.Add(MediaType.Photo); |
| 0 | 332 | | mediaTypes.Add(MediaType.Video); |
| 0 | 333 | | break; |
| | 334 | | default: |
| 0 | 335 | | mediaTypes.Add(MediaType.Video); |
| | 336 | | break; |
| | 337 | | } |
| | 338 | | } |
| | 339 | |
|
| 0 | 340 | | mediaTypes = mediaTypes.Distinct().ToList(); |
| | 341 | | } |
| | 342 | |
|
| 0 | 343 | | var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 |
| 0 | 344 | | ? new[] |
| 0 | 345 | | { |
| 0 | 346 | | BaseItemKind.Person, |
| 0 | 347 | | BaseItemKind.Studio, |
| 0 | 348 | | BaseItemKind.Year, |
| 0 | 349 | | BaseItemKind.MusicGenre, |
| 0 | 350 | | BaseItemKind.Genre |
| 0 | 351 | | } |
| 0 | 352 | | : Array.Empty<BaseItemKind>(); |
| | 353 | |
|
| 0 | 354 | | var query = new InternalItemsQuery(user) |
| 0 | 355 | | { |
| 0 | 356 | | IncludeItemTypes = includeItemTypes, |
| 0 | 357 | | OrderBy = new[] |
| 0 | 358 | | { |
| 0 | 359 | | (ItemSortBy.DateCreated, SortOrder.Descending), |
| 0 | 360 | | (ItemSortBy.SortName, SortOrder.Descending), |
| 0 | 361 | | (ItemSortBy.ProductionYear, SortOrder.Descending) |
| 0 | 362 | | }, |
| 0 | 363 | | IsFolder = includeItemTypes.Length == 0 ? false : null, |
| 0 | 364 | | ExcludeItemTypes = excludeItemTypes, |
| 0 | 365 | | IsVirtualItem = false, |
| 0 | 366 | | Limit = limit * 5, |
| 0 | 367 | | IsPlayed = isPlayed, |
| 0 | 368 | | DtoOptions = options, |
| 0 | 369 | | MediaTypes = mediaTypes.ToArray() |
| 0 | 370 | | }; |
| | 371 | |
|
| 0 | 372 | | if (parents.Count == 0) |
| | 373 | | { |
| 0 | 374 | | return _libraryManager.GetItemList(query, false); |
| | 375 | | } |
| | 376 | |
|
| 0 | 377 | | return _libraryManager.GetItemList(query, parents); |
| | 378 | | } |
| | 379 | | } |
| | 380 | | } |