LibraryService.cs 44 KB

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