Playlist.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. #nullable disable
  2. #pragma warning disable CS1591
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text.Json.Serialization;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using Jellyfin.Data;
  11. using Jellyfin.Data.Enums;
  12. using Jellyfin.Database.Implementations.Entities;
  13. using Jellyfin.Database.Implementations.Enums;
  14. using MediaBrowser.Controller.Dto;
  15. using MediaBrowser.Controller.Entities;
  16. using MediaBrowser.Controller.Entities.Audio;
  17. using MediaBrowser.Controller.Providers;
  18. using MediaBrowser.Model.Entities;
  19. namespace MediaBrowser.Controller.Playlists
  20. {
  21. public class Playlist : Folder, IHasShares
  22. {
  23. public static readonly IReadOnlyList<string> SupportedExtensions =
  24. [
  25. ".m3u",
  26. ".m3u8",
  27. ".pls",
  28. ".wpl",
  29. ".zpl"
  30. ];
  31. public Playlist()
  32. {
  33. Shares = [];
  34. OpenAccess = false;
  35. }
  36. public Guid OwnerUserId { get; set; }
  37. public bool OpenAccess { get; set; }
  38. public IReadOnlyList<PlaylistUserPermissions> Shares { get; set; }
  39. [JsonIgnore]
  40. public bool IsFile => IsPlaylistFile(Path);
  41. [JsonIgnore]
  42. public override string ContainingFolderPath
  43. {
  44. get
  45. {
  46. var path = Path;
  47. if (IsPlaylistFile(path))
  48. {
  49. return System.IO.Path.GetDirectoryName(path);
  50. }
  51. return path;
  52. }
  53. }
  54. [JsonIgnore]
  55. protected override bool FilterLinkedChildrenPerUser => true;
  56. [JsonIgnore]
  57. public override bool SupportsInheritedParentImages => false;
  58. [JsonIgnore]
  59. public override bool SupportsPlayedStatus => MediaType == Jellyfin.Data.Enums.MediaType.Video;
  60. [JsonIgnore]
  61. public override bool AlwaysScanInternalMetadataPath => true;
  62. [JsonIgnore]
  63. public override bool SupportsCumulativeRunTimeTicks => true;
  64. [JsonIgnore]
  65. public override bool IsPreSorted => true;
  66. public MediaType PlaylistMediaType { get; set; }
  67. [JsonIgnore]
  68. public override MediaType MediaType => PlaylistMediaType;
  69. [JsonIgnore]
  70. private bool IsSharedItem
  71. {
  72. get
  73. {
  74. var path = Path;
  75. if (string.IsNullOrEmpty(path))
  76. {
  77. return false;
  78. }
  79. return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
  80. }
  81. }
  82. public static bool IsPlaylistFile(string path)
  83. {
  84. // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
  85. return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
  86. }
  87. public void SetMediaType(MediaType? value)
  88. {
  89. PlaylistMediaType = value ?? MediaType.Unknown;
  90. }
  91. public override double GetDefaultPrimaryImageAspectRatio()
  92. {
  93. return 1;
  94. }
  95. public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders)
  96. {
  97. return true;
  98. }
  99. public override bool IsSaveLocalMetadataEnabled()
  100. {
  101. return true;
  102. }
  103. protected override List<BaseItem> LoadChildren()
  104. {
  105. // Save a trip to the database
  106. return [];
  107. }
  108. protected override Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMetadata, bool allowRemoveRoot, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService, CancellationToken cancellationToken)
  109. {
  110. return Task.CompletedTask;
  111. }
  112. public override IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
  113. {
  114. return GetPlayableItems(user, query);
  115. }
  116. protected override IReadOnlyList<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
  117. {
  118. return [];
  119. }
  120. public override IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
  121. {
  122. return GetPlayableItems(user, query);
  123. }
  124. public IReadOnlyList<Tuple<LinkedChild, BaseItem>> GetManageableItems()
  125. {
  126. return GetLinkedChildrenInfos();
  127. }
  128. private IReadOnlyList<BaseItem> GetPlayableItems(User user, InternalItemsQuery query)
  129. {
  130. query ??= new InternalItemsQuery(user);
  131. query.IsFolder = false;
  132. return base.GetChildren(user, true, query);
  133. }
  134. public static IReadOnlyList<BaseItem> GetPlaylistItems(IEnumerable<BaseItem> inputItems, User user, DtoOptions options)
  135. {
  136. if (user is not null)
  137. {
  138. inputItems = inputItems.Where(i => i.IsVisible(user));
  139. }
  140. var list = new List<BaseItem>();
  141. foreach (var item in inputItems)
  142. {
  143. var playlistItems = GetPlaylistItems(item, user, options);
  144. list.AddRange(playlistItems);
  145. }
  146. return list;
  147. }
  148. private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem item, User user, DtoOptions options)
  149. {
  150. if (item is MusicGenre musicGenre)
  151. {
  152. return LibraryManager.GetItemList(new InternalItemsQuery(user)
  153. {
  154. Recursive = true,
  155. IncludeItemTypes = [BaseItemKind.Audio],
  156. GenreIds = [musicGenre.Id],
  157. OrderBy = [(ItemSortBy.AlbumArtist, SortOrder.Ascending), (ItemSortBy.Album, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending)],
  158. DtoOptions = options
  159. });
  160. }
  161. if (item is MusicArtist musicArtist)
  162. {
  163. return LibraryManager.GetItemList(new InternalItemsQuery(user)
  164. {
  165. Recursive = true,
  166. IncludeItemTypes = [BaseItemKind.Audio],
  167. ArtistIds = [musicArtist.Id],
  168. OrderBy = [(ItemSortBy.AlbumArtist, SortOrder.Ascending), (ItemSortBy.Album, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending)],
  169. DtoOptions = options
  170. });
  171. }
  172. if (item is Folder folder)
  173. {
  174. var query = new InternalItemsQuery(user)
  175. {
  176. Recursive = true,
  177. IsFolder = false,
  178. MediaTypes = [MediaType.Audio, MediaType.Video],
  179. EnableTotalRecordCount = false,
  180. DtoOptions = options
  181. };
  182. return folder.GetItemList(query);
  183. }
  184. return [item];
  185. }
  186. public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
  187. {
  188. if (!IsSharedItem)
  189. {
  190. return base.IsVisible(user, skipAllowedTagsCheck);
  191. }
  192. if (OpenAccess)
  193. {
  194. return true;
  195. }
  196. var userId = user.Id;
  197. if (userId.Equals(OwnerUserId))
  198. {
  199. return true;
  200. }
  201. var shares = Shares;
  202. if (shares.Count == 0)
  203. {
  204. return false;
  205. }
  206. return shares.Any(s => s.UserId.Equals(userId));
  207. }
  208. public override bool CanDelete(User user)
  209. {
  210. return user.HasPermission(PermissionKind.IsAdministrator) || user.Id.Equals(OwnerUserId);
  211. }
  212. public override bool IsVisibleStandalone(User user)
  213. {
  214. if (!IsSharedItem)
  215. {
  216. return base.IsVisibleStandalone(user);
  217. }
  218. return IsVisible(user);
  219. }
  220. }
  221. }