LibraryService.cs 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Text.RegularExpressions;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using MediaBrowser.Api.Movies;
  10. using MediaBrowser.Common.Extensions;
  11. using MediaBrowser.Common.Progress;
  12. using MediaBrowser.Controller.Configuration;
  13. using MediaBrowser.Controller.Dto;
  14. using MediaBrowser.Controller.Entities;
  15. using MediaBrowser.Controller.Entities.Audio;
  16. using MediaBrowser.Controller.Entities.Movies;
  17. using MediaBrowser.Controller.Entities.TV;
  18. using MediaBrowser.Controller.Library;
  19. using MediaBrowser.Controller.LiveTv;
  20. using MediaBrowser.Controller.Net;
  21. using MediaBrowser.Controller.Persistence;
  22. using MediaBrowser.Controller.Providers;
  23. using MediaBrowser.Controller.TV;
  24. using MediaBrowser.Model.Activity;
  25. using MediaBrowser.Model.Configuration;
  26. using MediaBrowser.Model.Dto;
  27. using MediaBrowser.Model.Entities;
  28. using MediaBrowser.Model.Globalization;
  29. using MediaBrowser.Model.IO;
  30. using MediaBrowser.Model.Querying;
  31. using MediaBrowser.Model.Services;
  32. using Microsoft.Extensions.Logging;
  33. using Microsoft.Net.Http.Headers;
  34. namespace MediaBrowser.Api.Library
  35. {
  36. [Route("/Items/{Id}/File", "GET", Summary = "Gets the original file of an item")]
  37. [Authenticated]
  38. public class GetFile
  39. {
  40. /// <summary>
  41. /// Gets or sets the id.
  42. /// </summary>
  43. /// <value>The id.</value>
  44. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  45. public string Id { get; set; }
  46. }
  47. /// <summary>
  48. /// Class GetCriticReviews
  49. /// </summary>
  50. [Route("/Items/{Id}/CriticReviews", "GET", Summary = "Gets critic reviews for an item")]
  51. [Authenticated]
  52. public class GetCriticReviews : IReturn<QueryResult<BaseItemDto>>
  53. {
  54. /// <summary>
  55. /// Gets or sets the id.
  56. /// </summary>
  57. /// <value>The id.</value>
  58. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  59. public string Id { get; set; }
  60. /// <summary>
  61. /// Skips over a given number of items within the results. Use for paging.
  62. /// </summary>
  63. /// <value>The start index.</value>
  64. [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
  65. public int? StartIndex { get; set; }
  66. /// <summary>
  67. /// The maximum number of items to return
  68. /// </summary>
  69. /// <value>The limit.</value>
  70. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
  71. public int? Limit { get; set; }
  72. }
  73. /// <summary>
  74. /// Class GetThemeSongs
  75. /// </summary>
  76. [Route("/Items/{Id}/ThemeSongs", "GET", Summary = "Gets theme songs for an item")]
  77. [Authenticated]
  78. public class GetThemeSongs : IReturn<ThemeMediaResult>
  79. {
  80. /// <summary>
  81. /// Gets or sets the user id.
  82. /// </summary>
  83. /// <value>The user id.</value>
  84. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  85. public Guid UserId { get; set; }
  86. /// <summary>
  87. /// Gets or sets the id.
  88. /// </summary>
  89. /// <value>The id.</value>
  90. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  91. public string Id { get; set; }
  92. [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  93. public bool InheritFromParent { get; set; }
  94. }
  95. /// <summary>
  96. /// Class GetThemeVideos
  97. /// </summary>
  98. [Route("/Items/{Id}/ThemeVideos", "GET", Summary = "Gets theme videos for an item")]
  99. [Authenticated]
  100. public class GetThemeVideos : IReturn<ThemeMediaResult>
  101. {
  102. /// <summary>
  103. /// Gets or sets the user id.
  104. /// </summary>
  105. /// <value>The user id.</value>
  106. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  107. public Guid UserId { get; set; }
  108. /// <summary>
  109. /// Gets or sets the id.
  110. /// </summary>
  111. /// <value>The id.</value>
  112. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  113. public string Id { get; set; }
  114. [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  115. public bool InheritFromParent { get; set; }
  116. }
  117. /// <summary>
  118. /// Class GetThemeVideos
  119. /// </summary>
  120. [Route("/Items/{Id}/ThemeMedia", "GET", Summary = "Gets theme videos and songs for an item")]
  121. [Authenticated]
  122. public class GetThemeMedia : IReturn<AllThemeMediaResult>
  123. {
  124. /// <summary>
  125. /// Gets or sets the user id.
  126. /// </summary>
  127. /// <value>The user id.</value>
  128. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  129. public Guid UserId { get; set; }
  130. /// <summary>
  131. /// Gets or sets the id.
  132. /// </summary>
  133. /// <value>The id.</value>
  134. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  135. public string Id { get; set; }
  136. [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  137. public bool InheritFromParent { get; set; }
  138. }
  139. [Route("/Library/Refresh", "POST", Summary = "Starts a library scan")]
  140. [Authenticated(Roles = "Admin")]
  141. public class RefreshLibrary : IReturnVoid
  142. {
  143. }
  144. [Route("/Items/{Id}", "DELETE", Summary = "Deletes an item from the library and file system")]
  145. [Authenticated]
  146. public class DeleteItem : IReturnVoid
  147. {
  148. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
  149. public string Id { get; set; }
  150. }
  151. [Route("/Items", "DELETE", Summary = "Deletes an item from the library and file system")]
  152. [Authenticated]
  153. public class DeleteItems : IReturnVoid
  154. {
  155. [ApiMember(Name = "Ids", Description = "Ids", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
  156. public string Ids { get; set; }
  157. }
  158. [Route("/Items/Counts", "GET")]
  159. [Authenticated]
  160. public class GetItemCounts : IReturn<ItemCounts>
  161. {
  162. [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  163. public Guid UserId { get; set; }
  164. [ApiMember(Name = "IsFavorite", Description = "Optional. Get counts of favorite items", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
  165. public bool? IsFavorite { get; set; }
  166. }
  167. [Route("/Items/{Id}/Ancestors", "GET", Summary = "Gets all parents of an item")]
  168. [Authenticated]
  169. public class GetAncestors : IReturn<BaseItemDto[]>
  170. {
  171. /// <summary>
  172. /// Gets or sets the user id.
  173. /// </summary>
  174. /// <value>The user id.</value>
  175. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  176. public Guid UserId { get; set; }
  177. /// <summary>
  178. /// Gets or sets the id.
  179. /// </summary>
  180. /// <value>The id.</value>
  181. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  182. public string Id { get; set; }
  183. }
  184. /// <summary>
  185. /// Class GetPhyscialPaths
  186. /// </summary>
  187. [Route("/Library/PhysicalPaths", "GET", Summary = "Gets a list of physical paths from virtual folders")]
  188. [Authenticated(Roles = "Admin")]
  189. public class GetPhyscialPaths : IReturn<List<string>>
  190. {
  191. }
  192. [Route("/Library/MediaFolders", "GET", Summary = "Gets all user media folders.")]
  193. [Authenticated]
  194. public class GetMediaFolders : IReturn<QueryResult<BaseItemDto>>
  195. {
  196. [ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
  197. public bool? IsHidden { get; set; }
  198. }
  199. [Route("/Library/Series/Added", "POST", Summary = "Reports that new episodes of a series have been added by an external source")]
  200. [Route("/Library/Series/Updated", "POST", Summary = "Reports that new episodes of a series have been added by an external source")]
  201. [Authenticated]
  202. public class PostUpdatedSeries : IReturnVoid
  203. {
  204. [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")]
  205. public string TvdbId { get; set; }
  206. }
  207. [Route("/Library/Movies/Added", "POST", Summary = "Reports that new movies have been added by an external source")]
  208. [Route("/Library/Movies/Updated", "POST", Summary = "Reports that new movies have been added by an external source")]
  209. [Authenticated]
  210. public class PostUpdatedMovies : IReturnVoid
  211. {
  212. [ApiMember(Name = "TmdbId", Description = "Tmdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")]
  213. public string TmdbId { get; set; }
  214. [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")]
  215. public string ImdbId { get; set; }
  216. }
  217. public class MediaUpdateInfo
  218. {
  219. public string Path { get; set; }
  220. // Created, Modified, Deleted
  221. public string UpdateType { get; set; }
  222. }
  223. [Route("/Library/Media/Updated", "POST", Summary = "Reports that new movies have been added by an external source")]
  224. [Authenticated]
  225. public class PostUpdatedMedia : IReturnVoid
  226. {
  227. [ApiMember(Name = "Updates", Description = "A list of updated media paths", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")]
  228. public List<MediaUpdateInfo> Updates { get; set; }
  229. }
  230. [Route("/Items/{Id}/Download", "GET", Summary = "Downloads item media")]
  231. [Authenticated(Roles = "download")]
  232. public class GetDownload
  233. {
  234. /// <summary>
  235. /// Gets or sets the id.
  236. /// </summary>
  237. /// <value>The id.</value>
  238. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  239. public string Id { get; set; }
  240. }
  241. [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
  242. [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")]
  243. [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
  244. [Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")]
  245. [Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")]
  246. [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")]
  247. [Authenticated]
  248. public class GetSimilarItems : BaseGetSimilarItemsFromItem
  249. {
  250. }
  251. [Route("/Libraries/AvailableOptions", "GET")]
  252. [Authenticated(AllowBeforeStartupWizard = true)]
  253. public class GetLibraryOptionsInfo : IReturn<LibraryOptionsResult>
  254. {
  255. public string LibraryContentType { get; set; }
  256. public bool IsNewLibrary { get; set; }
  257. }
  258. public class LibraryOptionInfo
  259. {
  260. public string Name { get; set; }
  261. public bool DefaultEnabled { get; set; }
  262. }
  263. public class LibraryOptionsResult
  264. {
  265. public LibraryOptionInfo[] MetadataSavers { get; set; }
  266. public LibraryOptionInfo[] MetadataReaders { get; set; }
  267. public LibraryOptionInfo[] SubtitleFetchers { get; set; }
  268. public LibraryTypeOptions[] TypeOptions { get; set; }
  269. }
  270. public class LibraryTypeOptions
  271. {
  272. public string Type { get; set; }
  273. public LibraryOptionInfo[] MetadataFetchers { get; set; }
  274. public LibraryOptionInfo[] ImageFetchers { get; set; }
  275. public ImageType[] SupportedImageTypes { get; set; }
  276. public ImageOption[] DefaultImageOptions { get; set; }
  277. }
  278. /// <summary>
  279. /// Class LibraryService
  280. /// </summary>
  281. public class LibraryService : BaseApiService
  282. {
  283. private readonly IProviderManager _providerManager;
  284. private readonly ILibraryManager _libraryManager;
  285. private readonly IUserManager _userManager;
  286. private readonly IDtoService _dtoService;
  287. private readonly IAuthorizationContext _authContext;
  288. private readonly IActivityManager _activityManager;
  289. private readonly ILocalizationManager _localization;
  290. private readonly ILibraryMonitor _libraryMonitor;
  291. /// <summary>
  292. /// Initializes a new instance of the <see cref="LibraryService" /> class.
  293. /// </summary>
  294. public LibraryService(
  295. ILogger<LibraryService> logger,
  296. IServerConfigurationManager serverConfigurationManager,
  297. IHttpResultFactory httpResultFactory,
  298. IProviderManager providerManager,
  299. ILibraryManager libraryManager,
  300. IUserManager userManager,
  301. IDtoService dtoService,
  302. IAuthorizationContext authContext,
  303. IActivityManager activityManager,
  304. ILocalizationManager localization,
  305. ILibraryMonitor libraryMonitor)
  306. : base(logger, serverConfigurationManager, httpResultFactory)
  307. {
  308. _providerManager = providerManager;
  309. _libraryManager = libraryManager;
  310. _userManager = userManager;
  311. _dtoService = dtoService;
  312. _authContext = authContext;
  313. _activityManager = activityManager;
  314. _localization = localization;
  315. _libraryMonitor = libraryMonitor;
  316. }
  317. private string[] GetRepresentativeItemTypes(string contentType)
  318. {
  319. switch (contentType)
  320. {
  321. case CollectionType.BoxSets:
  322. return new string[] { "BoxSet" };
  323. case CollectionType.Playlists:
  324. return new string[] { "Playlist" };
  325. case CollectionType.Movies:
  326. return new string[] { "Movie" };
  327. case CollectionType.TvShows:
  328. return new string[] { "Series", "Season", "Episode" };
  329. case CollectionType.Books:
  330. return new string[] { "Book" };
  331. case CollectionType.Music:
  332. return new string[] { "MusicAlbum", "MusicArtist", "Audio", "MusicVideo" };
  333. case CollectionType.HomeVideos:
  334. case CollectionType.Photos:
  335. return new string[] { "Video", "Photo" };
  336. case CollectionType.MusicVideos:
  337. return new string[] { "MusicVideo" };
  338. default:
  339. return new string[] { "Series", "Season", "Episode", "Movie" };
  340. }
  341. }
  342. private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
  343. {
  344. if (isNewLibrary)
  345. {
  346. return false;
  347. }
  348. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  349. .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
  350. .ToArray();
  351. if (metadataOptions.Length == 0)
  352. {
  353. return true;
  354. }
  355. return metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase));
  356. }
  357. private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
  358. {
  359. if (isNewLibrary)
  360. {
  361. if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
  362. {
  363. if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
  364. {
  365. return true;
  366. }
  367. if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
  368. {
  369. return false;
  370. }
  371. if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
  372. {
  373. return false;
  374. }
  375. if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
  376. {
  377. return false;
  378. }
  379. return true;
  380. }
  381. else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
  382. {
  383. return true;
  384. }
  385. else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
  386. {
  387. return false;
  388. }
  389. else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
  390. {
  391. return true;
  392. }
  393. else if (string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase))
  394. {
  395. return true;
  396. }
  397. return false;
  398. }
  399. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  400. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  401. .ToArray();
  402. if (metadataOptions.Length == 0)
  403. {
  404. return true;
  405. }
  406. return metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
  407. }
  408. private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
  409. {
  410. if (isNewLibrary)
  411. {
  412. if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
  413. {
  414. if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
  415. {
  416. return false;
  417. }
  418. if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
  419. {
  420. return false;
  421. }
  422. if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
  423. {
  424. return false;
  425. }
  426. if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
  427. {
  428. return false;
  429. }
  430. return true;
  431. }
  432. else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
  433. {
  434. return true;
  435. }
  436. else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
  437. {
  438. return false;
  439. }
  440. else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
  441. {
  442. return true;
  443. }
  444. else if (string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase))
  445. {
  446. return true;
  447. }
  448. else if (string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase))
  449. {
  450. return true;
  451. }
  452. else if (string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase))
  453. {
  454. return true;
  455. }
  456. return false;
  457. }
  458. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  459. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  460. .ToArray();
  461. if (metadataOptions.Length == 0)
  462. {
  463. return true;
  464. }
  465. return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
  466. }
  467. public object Get(GetLibraryOptionsInfo request)
  468. {
  469. var result = new LibraryOptionsResult();
  470. var types = GetRepresentativeItemTypes(request.LibraryContentType);
  471. var isNewLibrary = request.IsNewLibrary;
  472. var typesList = types.ToList();
  473. var plugins = _providerManager.GetAllMetadataPlugins()
  474. .Where(i => types.Contains(i.ItemType, StringComparer.OrdinalIgnoreCase))
  475. .OrderBy(i => typesList.IndexOf(i.ItemType))
  476. .ToList();
  477. result.MetadataSavers = plugins
  478. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver))
  479. .Select(i => new LibraryOptionInfo
  480. {
  481. Name = i.Name,
  482. DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary)
  483. })
  484. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  485. .Select(x => x.First())
  486. .ToArray();
  487. result.MetadataReaders = plugins
  488. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider))
  489. .Select(i => new LibraryOptionInfo
  490. {
  491. Name = i.Name,
  492. DefaultEnabled = true
  493. })
  494. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  495. .Select(x => x.First())
  496. .ToArray();
  497. result.SubtitleFetchers = plugins
  498. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher))
  499. .Select(i => new LibraryOptionInfo
  500. {
  501. Name = i.Name,
  502. DefaultEnabled = true
  503. })
  504. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  505. .Select(x => x.First())
  506. .ToArray();
  507. var typeOptions = new List<LibraryTypeOptions>();
  508. foreach (var type in types)
  509. {
  510. ImageOption[] defaultImageOptions = null;
  511. TypeOptions.DefaultImageOptions.TryGetValue(type, out defaultImageOptions);
  512. typeOptions.Add(new LibraryTypeOptions
  513. {
  514. Type = type,
  515. MetadataFetchers = plugins
  516. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  517. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher))
  518. .Select(i => new LibraryOptionInfo
  519. {
  520. Name = i.Name,
  521. DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary)
  522. })
  523. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  524. .Select(x => x.First())
  525. .ToArray(),
  526. ImageFetchers = plugins
  527. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  528. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher))
  529. .Select(i => new LibraryOptionInfo
  530. {
  531. Name = i.Name,
  532. DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary)
  533. })
  534. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  535. .Select(x => x.First())
  536. .ToArray(),
  537. SupportedImageTypes = plugins
  538. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  539. .SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>())
  540. .Distinct()
  541. .ToArray(),
  542. DefaultImageOptions = defaultImageOptions ?? Array.Empty<ImageOption>()
  543. });
  544. }
  545. result.TypeOptions = typeOptions.ToArray();
  546. return result;
  547. }
  548. public object Get(GetSimilarItems request)
  549. {
  550. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  551. var item = string.IsNullOrEmpty(request.Id) ?
  552. (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
  553. _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
  554. var program = item as IHasProgramAttributes;
  555. if (item is Movie || (program != null && program.IsMovie) || item is Trailer)
  556. {
  557. return new MoviesService(
  558. Logger,
  559. ServerConfigurationManager,
  560. ResultFactory,
  561. _userManager,
  562. _libraryManager,
  563. _dtoService,
  564. _authContext)
  565. {
  566. Request = Request,
  567. }.GetSimilarItemsResult(request);
  568. }
  569. if (program != null && program.IsSeries)
  570. {
  571. return GetSimilarItemsResult(request, new[] { typeof(Series).Name });
  572. }
  573. if (item is Episode || (item is IItemByName && !(item is MusicArtist)))
  574. {
  575. return new QueryResult<BaseItemDto>();
  576. }
  577. return GetSimilarItemsResult(request, new[] { item.GetType().Name });
  578. }
  579. private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, string[] includeItemTypes)
  580. {
  581. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  582. var item = string.IsNullOrEmpty(request.Id) ?
  583. (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
  584. _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
  585. var dtoOptions = GetDtoOptions(_authContext, request);
  586. var query = new InternalItemsQuery(user)
  587. {
  588. Limit = request.Limit,
  589. IncludeItemTypes = includeItemTypes,
  590. SimilarTo = item,
  591. DtoOptions = dtoOptions,
  592. EnableTotalRecordCount = false
  593. };
  594. // ExcludeArtistIds
  595. if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
  596. {
  597. query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds);
  598. }
  599. List<BaseItem> itemsResult;
  600. if (item is MusicArtist)
  601. {
  602. query.IncludeItemTypes = Array.Empty<string>();
  603. itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList();
  604. }
  605. else
  606. {
  607. itemsResult = _libraryManager.GetItemList(query);
  608. }
  609. var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
  610. var result = new QueryResult<BaseItemDto>
  611. {
  612. Items = returnList,
  613. TotalRecordCount = itemsResult.Count
  614. };
  615. return result;
  616. }
  617. public object Get(GetMediaFolders request)
  618. {
  619. var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList();
  620. if (request.IsHidden.HasValue)
  621. {
  622. var val = request.IsHidden.Value;
  623. items = items.Where(i => i.IsHidden == val).ToList();
  624. }
  625. var dtoOptions = GetDtoOptions(_authContext, request);
  626. var result = new QueryResult<BaseItemDto>
  627. {
  628. TotalRecordCount = items.Count,
  629. Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray()
  630. };
  631. return result;
  632. }
  633. public void Post(PostUpdatedSeries request)
  634. {
  635. var series = _libraryManager.GetItemList(new InternalItemsQuery
  636. {
  637. IncludeItemTypes = new[] { typeof(Series).Name },
  638. DtoOptions = new DtoOptions(false)
  639. {
  640. EnableImages = false
  641. }
  642. }).Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray();
  643. foreach (var item in series)
  644. {
  645. _libraryMonitor.ReportFileSystemChanged(item.Path);
  646. }
  647. }
  648. public void Post(PostUpdatedMedia request)
  649. {
  650. if (request.Updates != null)
  651. {
  652. foreach (var item in request.Updates)
  653. {
  654. _libraryMonitor.ReportFileSystemChanged(item.Path);
  655. }
  656. }
  657. }
  658. public void Post(PostUpdatedMovies request)
  659. {
  660. var movies = _libraryManager.GetItemList(new InternalItemsQuery
  661. {
  662. IncludeItemTypes = new[] { typeof(Movie).Name },
  663. DtoOptions = new DtoOptions(false)
  664. {
  665. EnableImages = false
  666. }
  667. });
  668. if (!string.IsNullOrWhiteSpace(request.ImdbId))
  669. {
  670. movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToList();
  671. }
  672. else if (!string.IsNullOrWhiteSpace(request.TmdbId))
  673. {
  674. movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList();
  675. }
  676. else
  677. {
  678. movies = new List<BaseItem>();
  679. }
  680. foreach (var item in movies)
  681. {
  682. _libraryMonitor.ReportFileSystemChanged(item.Path);
  683. }
  684. }
  685. public Task<object> Get(GetDownload request)
  686. {
  687. var item = _libraryManager.GetItemById(request.Id);
  688. var auth = _authContext.GetAuthorizationInfo(Request);
  689. var user = auth.User;
  690. if (user != null)
  691. {
  692. if (!item.CanDownload(user))
  693. {
  694. throw new ArgumentException("Item does not support downloading");
  695. }
  696. }
  697. else
  698. {
  699. if (!item.CanDownload())
  700. {
  701. throw new ArgumentException("Item does not support downloading");
  702. }
  703. }
  704. var headers = new Dictionary<string, string>();
  705. if (user != null)
  706. {
  707. LogDownload(item, user, auth);
  708. }
  709. var path = item.Path;
  710. // Quotes are valid in linux. They'll possibly cause issues here
  711. var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
  712. if (!string.IsNullOrWhiteSpace(filename))
  713. {
  714. // Kestrel doesn't support non-ASCII characters in headers
  715. if (Regex.IsMatch(filename, "[^[:ascii:]]"))
  716. {
  717. // Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
  718. headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
  719. }
  720. else
  721. {
  722. headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
  723. }
  724. }
  725. return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
  726. {
  727. Path = path,
  728. ResponseHeaders = headers
  729. });
  730. }
  731. private void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
  732. {
  733. try
  734. {
  735. _activityManager.Create(new ActivityLogEntry
  736. {
  737. Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
  738. Type = "UserDownloadingContent",
  739. ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
  740. UserId = auth.UserId
  741. });
  742. }
  743. catch
  744. {
  745. // Logged at lower levels
  746. }
  747. }
  748. public Task<object> Get(GetFile request)
  749. {
  750. var item = _libraryManager.GetItemById(request.Id);
  751. return ResultFactory.GetStaticFileResult(Request, item.Path);
  752. }
  753. /// <summary>
  754. /// Gets the specified request.
  755. /// </summary>
  756. /// <param name="request">The request.</param>
  757. /// <returns>System.Object.</returns>
  758. public object Get(GetPhyscialPaths request)
  759. {
  760. var result = _libraryManager.RootFolder.Children
  761. .SelectMany(c => c.PhysicalLocations)
  762. .ToList();
  763. return ToOptimizedResult(result);
  764. }
  765. /// <summary>
  766. /// Gets the specified request.
  767. /// </summary>
  768. /// <param name="request">The request.</param>
  769. /// <returns>System.Object.</returns>
  770. public object Get(GetAncestors request)
  771. {
  772. var result = GetAncestors(request);
  773. return ToOptimizedResult(result);
  774. }
  775. /// <summary>
  776. /// Gets the ancestors.
  777. /// </summary>
  778. /// <param name="request">The request.</param>
  779. /// <returns>Task{BaseItemDto[]}.</returns>
  780. public List<BaseItemDto> GetAncestors(GetAncestors request)
  781. {
  782. var item = _libraryManager.GetItemById(request.Id);
  783. var baseItemDtos = new List<BaseItemDto>();
  784. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  785. var dtoOptions = GetDtoOptions(_authContext, request);
  786. BaseItem parent = item.GetParent();
  787. while (parent != null)
  788. {
  789. if (user != null)
  790. {
  791. parent = TranslateParentItem(parent, user);
  792. }
  793. baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
  794. parent = parent.GetParent();
  795. }
  796. return baseItemDtos;
  797. }
  798. private BaseItem TranslateParentItem(BaseItem item, User user)
  799. {
  800. if (item.GetParent() is AggregateFolder)
  801. {
  802. return _libraryManager.GetUserRootFolder().GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
  803. }
  804. return item;
  805. }
  806. /// <summary>
  807. /// Gets the specified request.
  808. /// </summary>
  809. /// <param name="request">The request.</param>
  810. /// <returns>System.Object.</returns>
  811. public object Get(GetCriticReviews request)
  812. {
  813. return new QueryResult<BaseItemDto>();
  814. }
  815. /// <summary>
  816. /// Gets the specified request.
  817. /// </summary>
  818. /// <param name="request">The request.</param>
  819. /// <returns>System.Object.</returns>
  820. public object Get(GetItemCounts request)
  821. {
  822. var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId);
  823. var counts = new ItemCounts
  824. {
  825. AlbumCount = GetCount(typeof(MusicAlbum), user, request),
  826. EpisodeCount = GetCount(typeof(Episode), user, request),
  827. MovieCount = GetCount(typeof(Movie), user, request),
  828. SeriesCount = GetCount(typeof(Series), user, request),
  829. SongCount = GetCount(typeof(Audio), user, request),
  830. MusicVideoCount = GetCount(typeof(MusicVideo), user, request),
  831. BoxSetCount = GetCount(typeof(BoxSet), user, request),
  832. BookCount = GetCount(typeof(Book), user, request)
  833. };
  834. return ToOptimizedResult(counts);
  835. }
  836. private int GetCount(Type type, User user, GetItemCounts request)
  837. {
  838. var query = new InternalItemsQuery(user)
  839. {
  840. IncludeItemTypes = new[] { type.Name },
  841. Limit = 0,
  842. Recursive = true,
  843. IsVirtualItem = false,
  844. IsFavorite = request.IsFavorite,
  845. DtoOptions = new DtoOptions(false)
  846. {
  847. EnableImages = false
  848. }
  849. };
  850. return _libraryManager.GetItemsResult(query).TotalRecordCount;
  851. }
  852. /// <summary>
  853. /// Posts the specified request.
  854. /// </summary>
  855. /// <param name="request">The request.</param>
  856. public async Task Post(RefreshLibrary request)
  857. {
  858. try
  859. {
  860. await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false);
  861. }
  862. catch (Exception ex)
  863. {
  864. Logger.LogError(ex, "Error refreshing library");
  865. }
  866. }
  867. /// <summary>
  868. /// Deletes the specified request.
  869. /// </summary>
  870. /// <param name="request">The request.</param>
  871. public void Delete(DeleteItems request)
  872. {
  873. var ids = string.IsNullOrWhiteSpace(request.Ids)
  874. ? Array.Empty<string>()
  875. : request.Ids.Split(',');
  876. foreach (var i in ids)
  877. {
  878. var item = _libraryManager.GetItemById(i);
  879. var auth = _authContext.GetAuthorizationInfo(Request);
  880. var user = auth.User;
  881. if (!item.CanDelete(user))
  882. {
  883. if (ids.Length > 1)
  884. {
  885. throw new SecurityException("Unauthorized access");
  886. }
  887. continue;
  888. }
  889. _libraryManager.DeleteItem(item, new DeleteOptions
  890. {
  891. DeleteFileLocation = true
  892. }, true);
  893. }
  894. }
  895. /// <summary>
  896. /// Deletes the specified request.
  897. /// </summary>
  898. /// <param name="request">The request.</param>
  899. public void Delete(DeleteItem request)
  900. {
  901. Delete(new DeleteItems
  902. {
  903. Ids = request.Id
  904. });
  905. }
  906. public object Get(GetThemeMedia request)
  907. {
  908. var themeSongs = GetThemeSongs(new GetThemeSongs
  909. {
  910. InheritFromParent = request.InheritFromParent,
  911. Id = request.Id,
  912. UserId = request.UserId
  913. });
  914. var themeVideos = GetThemeVideos(new GetThemeVideos
  915. {
  916. InheritFromParent = request.InheritFromParent,
  917. Id = request.Id,
  918. UserId = request.UserId
  919. });
  920. return ToOptimizedResult(new AllThemeMediaResult
  921. {
  922. ThemeSongsResult = themeSongs,
  923. ThemeVideosResult = themeVideos,
  924. SoundtrackSongsResult = new ThemeMediaResult()
  925. });
  926. }
  927. /// <summary>
  928. /// Gets the specified request.
  929. /// </summary>
  930. /// <param name="request">The request.</param>
  931. /// <returns>System.Object.</returns>
  932. public object Get(GetThemeSongs request)
  933. {
  934. var result = GetThemeSongs(request);
  935. return ToOptimizedResult(result);
  936. }
  937. private ThemeMediaResult GetThemeSongs(GetThemeSongs request)
  938. {
  939. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  940. var item = string.IsNullOrEmpty(request.Id)
  941. ? (!request.UserId.Equals(Guid.Empty)
  942. ? _libraryManager.GetUserRootFolder()
  943. : (Folder)_libraryManager.RootFolder)
  944. : _libraryManager.GetItemById(request.Id);
  945. if (item == null)
  946. {
  947. throw new ResourceNotFoundException("Item not found.");
  948. }
  949. BaseItem[] themeItems = Array.Empty<BaseItem>();
  950. while (true)
  951. {
  952. themeItems = item.GetThemeSongs().ToArray();
  953. if (themeItems.Length > 0)
  954. {
  955. break;
  956. }
  957. if (!request.InheritFromParent)
  958. {
  959. break;
  960. }
  961. var parent = item.GetParent();
  962. if (parent == null)
  963. {
  964. break;
  965. }
  966. item = parent;
  967. }
  968. var dtoOptions = GetDtoOptions(_authContext, request);
  969. var dtos = themeItems
  970. .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
  971. var items = dtos.ToArray();
  972. return new ThemeMediaResult
  973. {
  974. Items = items,
  975. TotalRecordCount = items.Length,
  976. OwnerId = item.Id
  977. };
  978. }
  979. /// <summary>
  980. /// Gets the specified request.
  981. /// </summary>
  982. /// <param name="request">The request.</param>
  983. /// <returns>System.Object.</returns>
  984. public object Get(GetThemeVideos request)
  985. {
  986. var result = GetThemeVideos(request);
  987. return ToOptimizedResult(result);
  988. }
  989. public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
  990. {
  991. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  992. var item = string.IsNullOrEmpty(request.Id)
  993. ? (!request.UserId.Equals(Guid.Empty)
  994. ? _libraryManager.GetUserRootFolder()
  995. : (Folder)_libraryManager.RootFolder)
  996. : _libraryManager.GetItemById(request.Id);
  997. if (item == null)
  998. {
  999. throw new ResourceNotFoundException("Item not found.");
  1000. }
  1001. BaseItem[] themeItems = Array.Empty<BaseItem>();
  1002. while (true)
  1003. {
  1004. themeItems = item.GetThemeVideos().ToArray();
  1005. if (themeItems.Length > 0)
  1006. {
  1007. break;
  1008. }
  1009. if (!request.InheritFromParent)
  1010. {
  1011. break;
  1012. }
  1013. var parent = item.GetParent();
  1014. if (parent == null)
  1015. {
  1016. break;
  1017. }
  1018. item = parent;
  1019. }
  1020. var dtoOptions = GetDtoOptions(_authContext, request);
  1021. var dtos = themeItems
  1022. .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
  1023. var items = dtos.ToArray();
  1024. return new ThemeMediaResult
  1025. {
  1026. Items = items,
  1027. TotalRecordCount = items.Length,
  1028. OwnerId = item.Id
  1029. };
  1030. }
  1031. }
  1032. }