TVSeriesManager.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using Jellyfin.Data.Entities;
  6. using Jellyfin.Data.Enums;
  7. using MediaBrowser.Controller.Configuration;
  8. using MediaBrowser.Controller.Dto;
  9. using MediaBrowser.Controller.Entities;
  10. using MediaBrowser.Controller.Library;
  11. using MediaBrowser.Controller.TV;
  12. using MediaBrowser.Model.Querying;
  13. using Episode = MediaBrowser.Controller.Entities.TV.Episode;
  14. using Series = MediaBrowser.Controller.Entities.TV.Series;
  15. namespace Emby.Server.Implementations.TV
  16. {
  17. public class TVSeriesManager : ITVSeriesManager
  18. {
  19. private readonly IUserManager _userManager;
  20. private readonly IUserDataManager _userDataManager;
  21. private readonly ILibraryManager _libraryManager;
  22. private readonly IServerConfigurationManager _configurationManager;
  23. public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager)
  24. {
  25. _userManager = userManager;
  26. _userDataManager = userDataManager;
  27. _libraryManager = libraryManager;
  28. _configurationManager = configurationManager;
  29. }
  30. public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
  31. {
  32. var user = _userManager.GetUserById(request.UserId);
  33. if (user == null)
  34. {
  35. throw new ArgumentException("User not found");
  36. }
  37. string presentationUniqueKey = null;
  38. if (!string.IsNullOrEmpty(request.SeriesId))
  39. {
  40. if (_libraryManager.GetItemById(request.SeriesId) is Series series)
  41. {
  42. presentationUniqueKey = GetUniqueSeriesKey(series);
  43. }
  44. }
  45. if (!string.IsNullOrEmpty(presentationUniqueKey))
  46. {
  47. return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
  48. }
  49. BaseItem[] parents;
  50. if (request.ParentId.HasValue)
  51. {
  52. var parent = _libraryManager.GetItemById(request.ParentId.Value);
  53. if (parent != null)
  54. {
  55. parents = new[] { parent };
  56. }
  57. else
  58. {
  59. parents = Array.Empty<BaseItem>();
  60. }
  61. }
  62. else
  63. {
  64. parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
  65. .Where(i => i is Folder)
  66. .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes).Contains(i.Id))
  67. .ToArray();
  68. }
  69. return GetNextUp(request, parents, dtoOptions);
  70. }
  71. public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions dtoOptions)
  72. {
  73. var user = _userManager.GetUserById(request.UserId);
  74. if (user == null)
  75. {
  76. throw new ArgumentException("User not found");
  77. }
  78. string presentationUniqueKey = null;
  79. int? limit = null;
  80. if (!string.IsNullOrEmpty(request.SeriesId))
  81. {
  82. if (_libraryManager.GetItemById(request.SeriesId) is Series series)
  83. {
  84. presentationUniqueKey = GetUniqueSeriesKey(series);
  85. limit = 1;
  86. }
  87. }
  88. if (!string.IsNullOrEmpty(presentationUniqueKey))
  89. {
  90. return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
  91. }
  92. if (limit.HasValue)
  93. {
  94. limit = limit.Value + 10;
  95. }
  96. var items = _libraryManager
  97. .GetItemList(
  98. new InternalItemsQuery(user)
  99. {
  100. IncludeItemTypes = new[] { nameof(Episode) },
  101. OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
  102. SeriesPresentationUniqueKey = presentationUniqueKey,
  103. Limit = limit,
  104. DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableImages = false },
  105. GroupBySeriesPresentationUniqueKey = true
  106. }, parentsFolders.ToList())
  107. .Cast<Episode>()
  108. .Where(episode => !string.IsNullOrEmpty(episode.SeriesPresentationUniqueKey))
  109. .Select(GetUniqueSeriesKey);
  110. // Avoid implicitly captured closure
  111. var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
  112. return GetResult(episodes, request);
  113. }
  114. public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys, DtoOptions dtoOptions)
  115. {
  116. // Avoid implicitly captured closure
  117. var currentUser = user;
  118. var allNextUp = seriesKeys
  119. .Select(i => GetNextUp(i, currentUser, dtoOptions));
  120. // If viewing all next up for all series, remove first episodes
  121. // But if that returns empty, keep those first episodes (avoid completely empty view)
  122. var alwaysEnableFirstEpisode = !string.IsNullOrEmpty(request.SeriesId);
  123. var anyFound = false;
  124. return allNextUp
  125. .Where(i =>
  126. {
  127. if (request.DisableFirstEpisode)
  128. {
  129. return i.Item1 != DateTime.MinValue;
  130. }
  131. if (alwaysEnableFirstEpisode || i.Item1 != DateTime.MinValue)
  132. {
  133. anyFound = true;
  134. return true;
  135. }
  136. if (!anyFound && i.Item1 == DateTime.MinValue)
  137. {
  138. return true;
  139. }
  140. return false;
  141. })
  142. .Select(i => i.Item2())
  143. .Where(i => i != null);
  144. }
  145. private static string GetUniqueSeriesKey(Episode episode)
  146. {
  147. return episode.SeriesPresentationUniqueKey;
  148. }
  149. private static string GetUniqueSeriesKey(Series series)
  150. {
  151. return series.GetPresentationUniqueKey();
  152. }
  153. /// <summary>
  154. /// Gets the next up.
  155. /// </summary>
  156. /// <returns>Task{Episode}.</returns>
  157. private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions)
  158. {
  159. var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
  160. {
  161. AncestorWithPresentationUniqueKey = null,
  162. SeriesPresentationUniqueKey = seriesKey,
  163. IncludeItemTypes = new[] { nameof(Episode) },
  164. OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) },
  165. IsPlayed = true,
  166. Limit = 1,
  167. ParentIndexNumberNotEquals = 0,
  168. DtoOptions = new DtoOptions
  169. {
  170. Fields = new[] { ItemFields.SortName },
  171. EnableImages = false
  172. }
  173. }).Cast<Episode>().FirstOrDefault();
  174. Func<Episode> getEpisode = () =>
  175. {
  176. var nextEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
  177. {
  178. AncestorWithPresentationUniqueKey = null,
  179. SeriesPresentationUniqueKey = seriesKey,
  180. IncludeItemTypes = new[] { nameof(Episode) },
  181. OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) },
  182. Limit = 1,
  183. IsPlayed = false,
  184. IsVirtualItem = false,
  185. ParentIndexNumberNotEquals = 0,
  186. MinSortName = lastWatchedEpisode?.SortName,
  187. DtoOptions = dtoOptions
  188. }).Cast<Episode>().FirstOrDefault();
  189. if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
  190. {
  191. var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
  192. {
  193. AncestorWithPresentationUniqueKey = null,
  194. SeriesPresentationUniqueKey = seriesKey,
  195. ParentIndexNumber = 0,
  196. IncludeItemTypes = new[] { nameof(Episode) },
  197. IsPlayed = false,
  198. IsVirtualItem = false,
  199. DtoOptions = dtoOptions
  200. })
  201. .Cast<Episode>()
  202. .Where(episode => episode.AirsBeforeSeasonNumber != null || episode.AirsAfterSeasonNumber != null)
  203. .ToList();
  204. if (lastWatchedEpisode != null)
  205. {
  206. // Last watched episode is added, because there could be specials that aired before the last watched episode
  207. consideredEpisodes.Add(lastWatchedEpisode);
  208. }
  209. if (nextEpisode != null)
  210. {
  211. consideredEpisodes.Add(nextEpisode);
  212. }
  213. var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending) })
  214. .Cast<Episode>();
  215. if (lastWatchedEpisode != null)
  216. {
  217. sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => episode.Id != lastWatchedEpisode.Id).Skip(1);
  218. }
  219. nextEpisode = sortedConsideredEpisodes.FirstOrDefault();
  220. }
  221. if (nextEpisode != null)
  222. {
  223. var userData = _userDataManager.GetUserData(user, nextEpisode);
  224. if (userData.PlaybackPositionTicks > 0)
  225. {
  226. return null;
  227. }
  228. }
  229. return nextEpisode;
  230. };
  231. if (lastWatchedEpisode != null)
  232. {
  233. var userData = _userDataManager.GetUserData(user, lastWatchedEpisode);
  234. var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1);
  235. return new Tuple<DateTime, Func<Episode>>(lastWatchedDate, getEpisode);
  236. }
  237. // Return the first episode
  238. return new Tuple<DateTime, Func<Episode>>(DateTime.MinValue, getEpisode);
  239. }
  240. private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)
  241. {
  242. int totalCount = 0;
  243. if (query.EnableTotalRecordCount)
  244. {
  245. var list = items.ToList();
  246. totalCount = list.Count;
  247. items = list;
  248. }
  249. if (query.StartIndex.HasValue)
  250. {
  251. items = items.Skip(query.StartIndex.Value);
  252. }
  253. if (query.Limit.HasValue)
  254. {
  255. items = items.Take(query.Limit.Value);
  256. }
  257. return new QueryResult<BaseItem>
  258. {
  259. TotalRecordCount = totalCount,
  260. Items = items.ToArray()
  261. };
  262. }
  263. }
  264. }