LibraryService.cs 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105
  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. return contentType switch
  316. {
  317. CollectionType.BoxSets => new[] {"BoxSet"},
  318. CollectionType.Playlists => new[] {"Playlist"},
  319. CollectionType.Movies => new[] {"Movie"},
  320. CollectionType.TvShows => new[] {"Series", "Season", "Episode"},
  321. CollectionType.Books => new[] {"Book"},
  322. CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"},
  323. CollectionType.HomeVideos => new[] {"Video", "Photo"},
  324. CollectionType.Photos => new[] {"Video", "Photo"},
  325. CollectionType.MusicVideos => new[] {"MusicVideo"},
  326. _ => new[] {"Series", "Season", "Episode", "Movie"}
  327. };
  328. }
  329. private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
  330. {
  331. if (isNewLibrary)
  332. {
  333. return false;
  334. }
  335. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  336. .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
  337. .ToArray();
  338. if (metadataOptions.Length == 0)
  339. {
  340. return true;
  341. }
  342. return metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase));
  343. }
  344. private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
  345. {
  346. if (isNewLibrary)
  347. {
  348. if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
  349. {
  350. if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
  351. || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
  352. || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
  353. {
  354. return false;
  355. }
  356. return true;
  357. }
  358. if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
  359. || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
  360. || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase))
  361. {
  362. return true;
  363. }
  364. return false;
  365. }
  366. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  367. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  368. .ToArray();
  369. return metadataOptions.Length == 0
  370. || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
  371. }
  372. private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
  373. {
  374. if (isNewLibrary)
  375. {
  376. if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
  377. {
  378. if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)
  379. || string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
  380. || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
  381. || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
  382. {
  383. return false;
  384. }
  385. return true;
  386. }
  387. if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
  388. || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)
  389. || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
  390. || string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase)
  391. || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase))
  392. {
  393. return true;
  394. }
  395. return false;
  396. }
  397. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  398. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  399. .ToArray();
  400. if (metadataOptions.Length == 0)
  401. {
  402. return true;
  403. }
  404. return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
  405. }
  406. public object Get(GetLibraryOptionsInfo request)
  407. {
  408. var result = new LibraryOptionsResult();
  409. var types = GetRepresentativeItemTypes(request.LibraryContentType);
  410. var isNewLibrary = request.IsNewLibrary;
  411. var typesList = types.ToList();
  412. var plugins = _providerManager.GetAllMetadataPlugins()
  413. .Where(i => types.Contains(i.ItemType, StringComparer.OrdinalIgnoreCase))
  414. .OrderBy(i => typesList.IndexOf(i.ItemType))
  415. .ToList();
  416. result.MetadataSavers = plugins
  417. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver))
  418. .Select(i => new LibraryOptionInfo
  419. {
  420. Name = i.Name,
  421. DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary)
  422. })
  423. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  424. .Select(x => x.First())
  425. .ToArray();
  426. result.MetadataReaders = plugins
  427. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider))
  428. .Select(i => new LibraryOptionInfo
  429. {
  430. Name = i.Name,
  431. DefaultEnabled = true
  432. })
  433. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  434. .Select(x => x.First())
  435. .ToArray();
  436. result.SubtitleFetchers = plugins
  437. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher))
  438. .Select(i => new LibraryOptionInfo
  439. {
  440. Name = i.Name,
  441. DefaultEnabled = true
  442. })
  443. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  444. .Select(x => x.First())
  445. .ToArray();
  446. var typeOptions = new List<LibraryTypeOptions>();
  447. foreach (var type in types)
  448. {
  449. TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions);
  450. typeOptions.Add(new LibraryTypeOptions
  451. {
  452. Type = type,
  453. MetadataFetchers = plugins
  454. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  455. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher))
  456. .Select(i => new LibraryOptionInfo
  457. {
  458. Name = i.Name,
  459. DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary)
  460. })
  461. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  462. .Select(x => x.First())
  463. .ToArray(),
  464. ImageFetchers = plugins
  465. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  466. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher))
  467. .Select(i => new LibraryOptionInfo
  468. {
  469. Name = i.Name,
  470. DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary)
  471. })
  472. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  473. .Select(x => x.First())
  474. .ToArray(),
  475. SupportedImageTypes = plugins
  476. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  477. .SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>())
  478. .Distinct()
  479. .ToArray(),
  480. DefaultImageOptions = defaultImageOptions ?? Array.Empty<ImageOption>()
  481. });
  482. }
  483. result.TypeOptions = typeOptions.ToArray();
  484. return result;
  485. }
  486. public object Get(GetSimilarItems request)
  487. {
  488. var item = string.IsNullOrEmpty(request.Id) ?
  489. (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
  490. _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
  491. var program = item as IHasProgramAttributes;
  492. if (item is Movie || (program != null && program.IsMovie) || item is Trailer)
  493. {
  494. return new MoviesService(
  495. Logger,
  496. ServerConfigurationManager,
  497. ResultFactory,
  498. _userManager,
  499. _libraryManager,
  500. _dtoService,
  501. _authContext)
  502. {
  503. Request = Request,
  504. }.GetSimilarItemsResult(request);
  505. }
  506. if (program != null && program.IsSeries)
  507. {
  508. return GetSimilarItemsResult(request, new[] { typeof(Series).Name });
  509. }
  510. if (item is Episode || (item is IItemByName && !(item is MusicArtist)))
  511. {
  512. return new QueryResult<BaseItemDto>();
  513. }
  514. return GetSimilarItemsResult(request, new[] { item.GetType().Name });
  515. }
  516. private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, string[] includeItemTypes)
  517. {
  518. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  519. var item = string.IsNullOrEmpty(request.Id) ?
  520. (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
  521. _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
  522. var dtoOptions = GetDtoOptions(_authContext, request);
  523. var query = new InternalItemsQuery(user)
  524. {
  525. Limit = request.Limit,
  526. IncludeItemTypes = includeItemTypes,
  527. SimilarTo = item,
  528. DtoOptions = dtoOptions,
  529. EnableTotalRecordCount = false
  530. };
  531. // ExcludeArtistIds
  532. if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
  533. {
  534. query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
  535. }
  536. List<BaseItem> itemsResult;
  537. if (item is MusicArtist)
  538. {
  539. query.IncludeItemTypes = Array.Empty<string>();
  540. itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList();
  541. }
  542. else
  543. {
  544. itemsResult = _libraryManager.GetItemList(query);
  545. }
  546. var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
  547. var result = new QueryResult<BaseItemDto>
  548. {
  549. Items = returnList,
  550. TotalRecordCount = itemsResult.Count
  551. };
  552. return result;
  553. }
  554. public object Get(GetMediaFolders request)
  555. {
  556. var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList();
  557. if (request.IsHidden.HasValue)
  558. {
  559. var val = request.IsHidden.Value;
  560. items = items.Where(i => i.IsHidden == val).ToList();
  561. }
  562. var dtoOptions = GetDtoOptions(_authContext, request);
  563. var result = new QueryResult<BaseItemDto>
  564. {
  565. TotalRecordCount = items.Count,
  566. Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray()
  567. };
  568. return result;
  569. }
  570. public void Post(PostUpdatedSeries request)
  571. {
  572. var series = _libraryManager.GetItemList(new InternalItemsQuery
  573. {
  574. IncludeItemTypes = new[] { typeof(Series).Name },
  575. DtoOptions = new DtoOptions(false)
  576. {
  577. EnableImages = false
  578. }
  579. }).Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray();
  580. foreach (var item in series)
  581. {
  582. _libraryMonitor.ReportFileSystemChanged(item.Path);
  583. }
  584. }
  585. public void Post(PostUpdatedMedia request)
  586. {
  587. if (request.Updates != null)
  588. {
  589. foreach (var item in request.Updates)
  590. {
  591. _libraryMonitor.ReportFileSystemChanged(item.Path);
  592. }
  593. }
  594. }
  595. public void Post(PostUpdatedMovies request)
  596. {
  597. var movies = _libraryManager.GetItemList(new InternalItemsQuery
  598. {
  599. IncludeItemTypes = new[] { typeof(Movie).Name },
  600. DtoOptions = new DtoOptions(false)
  601. {
  602. EnableImages = false
  603. }
  604. });
  605. if (!string.IsNullOrWhiteSpace(request.ImdbId))
  606. {
  607. movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToList();
  608. }
  609. else if (!string.IsNullOrWhiteSpace(request.TmdbId))
  610. {
  611. movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList();
  612. }
  613. else
  614. {
  615. movies = new List<BaseItem>();
  616. }
  617. foreach (var item in movies)
  618. {
  619. _libraryMonitor.ReportFileSystemChanged(item.Path);
  620. }
  621. }
  622. public Task<object> Get(GetDownload request)
  623. {
  624. var item = _libraryManager.GetItemById(request.Id);
  625. var auth = _authContext.GetAuthorizationInfo(Request);
  626. var user = auth.User;
  627. if (user != null)
  628. {
  629. if (!item.CanDownload(user))
  630. {
  631. throw new ArgumentException("Item does not support downloading");
  632. }
  633. }
  634. else
  635. {
  636. if (!item.CanDownload())
  637. {
  638. throw new ArgumentException("Item does not support downloading");
  639. }
  640. }
  641. var headers = new Dictionary<string, string>();
  642. if (user != null)
  643. {
  644. LogDownload(item, user, auth);
  645. }
  646. var path = item.Path;
  647. // Quotes are valid in linux. They'll possibly cause issues here
  648. var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
  649. if (!string.IsNullOrWhiteSpace(filename))
  650. {
  651. // Kestrel doesn't support non-ASCII characters in headers
  652. if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]"))
  653. {
  654. // Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
  655. headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
  656. }
  657. else
  658. {
  659. headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
  660. }
  661. }
  662. return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
  663. {
  664. Path = path,
  665. ResponseHeaders = headers
  666. });
  667. }
  668. private void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
  669. {
  670. try
  671. {
  672. _activityManager.Create(new ActivityLogEntry
  673. {
  674. Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
  675. Type = "UserDownloadingContent",
  676. ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
  677. UserId = auth.UserId
  678. });
  679. }
  680. catch
  681. {
  682. // Logged at lower levels
  683. }
  684. }
  685. public Task<object> Get(GetFile request)
  686. {
  687. var item = _libraryManager.GetItemById(request.Id);
  688. return ResultFactory.GetStaticFileResult(Request, item.Path);
  689. }
  690. /// <summary>
  691. /// Gets the specified request.
  692. /// </summary>
  693. /// <param name="request">The request.</param>
  694. /// <returns>System.Object.</returns>
  695. public object Get(GetPhyscialPaths request)
  696. {
  697. var result = _libraryManager.RootFolder.Children
  698. .SelectMany(c => c.PhysicalLocations)
  699. .ToList();
  700. return ToOptimizedResult(result);
  701. }
  702. /// <summary>
  703. /// Gets the specified request.
  704. /// </summary>
  705. /// <param name="request">The request.</param>
  706. /// <returns>System.Object.</returns>
  707. public object Get(GetAncestors request)
  708. {
  709. var result = GetAncestors(request);
  710. return ToOptimizedResult(result);
  711. }
  712. /// <summary>
  713. /// Gets the ancestors.
  714. /// </summary>
  715. /// <param name="request">The request.</param>
  716. /// <returns>Task{BaseItemDto[]}.</returns>
  717. public List<BaseItemDto> GetAncestors(GetAncestors request)
  718. {
  719. var item = _libraryManager.GetItemById(request.Id);
  720. var baseItemDtos = new List<BaseItemDto>();
  721. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  722. var dtoOptions = GetDtoOptions(_authContext, request);
  723. BaseItem parent = item.GetParent();
  724. while (parent != null)
  725. {
  726. if (user != null)
  727. {
  728. parent = TranslateParentItem(parent, user);
  729. }
  730. baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
  731. parent = parent.GetParent();
  732. }
  733. return baseItemDtos;
  734. }
  735. private BaseItem TranslateParentItem(BaseItem item, User user)
  736. {
  737. return item.GetParent() is AggregateFolder
  738. ? _libraryManager.GetUserRootFolder().GetChildren(user, true)
  739. .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path))
  740. : item;
  741. }
  742. /// <summary>
  743. /// Gets the specified request.
  744. /// </summary>
  745. /// <param name="request">The request.</param>
  746. /// <returns>System.Object.</returns>
  747. public object Get(GetCriticReviews request)
  748. {
  749. return new QueryResult<BaseItemDto>();
  750. }
  751. /// <summary>
  752. /// Gets the specified request.
  753. /// </summary>
  754. /// <param name="request">The request.</param>
  755. /// <returns>System.Object.</returns>
  756. public object Get(GetItemCounts request)
  757. {
  758. var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId);
  759. var counts = new ItemCounts
  760. {
  761. AlbumCount = GetCount(typeof(MusicAlbum), user, request),
  762. EpisodeCount = GetCount(typeof(Episode), user, request),
  763. MovieCount = GetCount(typeof(Movie), user, request),
  764. SeriesCount = GetCount(typeof(Series), user, request),
  765. SongCount = GetCount(typeof(Audio), user, request),
  766. MusicVideoCount = GetCount(typeof(MusicVideo), user, request),
  767. BoxSetCount = GetCount(typeof(BoxSet), user, request),
  768. BookCount = GetCount(typeof(Book), user, request)
  769. };
  770. return ToOptimizedResult(counts);
  771. }
  772. private int GetCount(Type type, User user, GetItemCounts request)
  773. {
  774. var query = new InternalItemsQuery(user)
  775. {
  776. IncludeItemTypes = new[] { type.Name },
  777. Limit = 0,
  778. Recursive = true,
  779. IsVirtualItem = false,
  780. IsFavorite = request.IsFavorite,
  781. DtoOptions = new DtoOptions(false)
  782. {
  783. EnableImages = false
  784. }
  785. };
  786. return _libraryManager.GetItemsResult(query).TotalRecordCount;
  787. }
  788. /// <summary>
  789. /// Posts the specified request.
  790. /// </summary>
  791. /// <param name="request">The request.</param>
  792. public async Task Post(RefreshLibrary request)
  793. {
  794. try
  795. {
  796. await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false);
  797. }
  798. catch (Exception ex)
  799. {
  800. Logger.LogError(ex, "Error refreshing library");
  801. }
  802. }
  803. /// <summary>
  804. /// Deletes the specified request.
  805. /// </summary>
  806. /// <param name="request">The request.</param>
  807. public void Delete(DeleteItems request)
  808. {
  809. var ids = string.IsNullOrWhiteSpace(request.Ids)
  810. ? Array.Empty<string>()
  811. : request.Ids.Split(',');
  812. foreach (var i in ids)
  813. {
  814. var item = _libraryManager.GetItemById(i);
  815. var auth = _authContext.GetAuthorizationInfo(Request);
  816. var user = auth.User;
  817. if (!item.CanDelete(user))
  818. {
  819. if (ids.Length > 1)
  820. {
  821. throw new SecurityException("Unauthorized access");
  822. }
  823. continue;
  824. }
  825. _libraryManager.DeleteItem(item, new DeleteOptions
  826. {
  827. DeleteFileLocation = true
  828. }, true);
  829. }
  830. }
  831. /// <summary>
  832. /// Deletes the specified request.
  833. /// </summary>
  834. /// <param name="request">The request.</param>
  835. public void Delete(DeleteItem request)
  836. {
  837. Delete(new DeleteItems
  838. {
  839. Ids = request.Id
  840. });
  841. }
  842. public object Get(GetThemeMedia request)
  843. {
  844. var themeSongs = GetThemeSongs(new GetThemeSongs
  845. {
  846. InheritFromParent = request.InheritFromParent,
  847. Id = request.Id,
  848. UserId = request.UserId
  849. });
  850. var themeVideos = GetThemeVideos(new GetThemeVideos
  851. {
  852. InheritFromParent = request.InheritFromParent,
  853. Id = request.Id,
  854. UserId = request.UserId
  855. });
  856. return ToOptimizedResult(new AllThemeMediaResult
  857. {
  858. ThemeSongsResult = themeSongs,
  859. ThemeVideosResult = themeVideos,
  860. SoundtrackSongsResult = new ThemeMediaResult()
  861. });
  862. }
  863. /// <summary>
  864. /// Gets the specified request.
  865. /// </summary>
  866. /// <param name="request">The request.</param>
  867. /// <returns>System.Object.</returns>
  868. public object Get(GetThemeSongs request)
  869. {
  870. var result = GetThemeSongs(request);
  871. return ToOptimizedResult(result);
  872. }
  873. private ThemeMediaResult GetThemeSongs(GetThemeSongs request)
  874. {
  875. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  876. var item = string.IsNullOrEmpty(request.Id)
  877. ? (!request.UserId.Equals(Guid.Empty)
  878. ? _libraryManager.GetUserRootFolder()
  879. : _libraryManager.RootFolder)
  880. : _libraryManager.GetItemById(request.Id);
  881. if (item == null)
  882. {
  883. throw new ResourceNotFoundException("Item not found.");
  884. }
  885. IEnumerable<BaseItem> themeItems = item.GetThemeSongs();
  886. while (!themeItems.Any() && request.InheritFromParent && item.GetParent() != null)
  887. {
  888. item = item.GetParent();
  889. themeItems = item.GetThemeSongs();
  890. }
  891. var dtoOptions = GetDtoOptions(_authContext, request);
  892. var items = themeItems
  893. .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
  894. .ToArray();
  895. return new ThemeMediaResult
  896. {
  897. Items = items,
  898. TotalRecordCount = items.Length,
  899. OwnerId = item.Id
  900. };
  901. }
  902. /// <summary>
  903. /// Gets the specified request.
  904. /// </summary>
  905. /// <param name="request">The request.</param>
  906. /// <returns>System.Object.</returns>
  907. public object Get(GetThemeVideos request)
  908. {
  909. return ToOptimizedResult(GetThemeVideos(request));
  910. }
  911. public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
  912. {
  913. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  914. var item = string.IsNullOrEmpty(request.Id)
  915. ? (!request.UserId.Equals(Guid.Empty)
  916. ? _libraryManager.GetUserRootFolder()
  917. : _libraryManager.RootFolder)
  918. : _libraryManager.GetItemById(request.Id);
  919. if (item == null)
  920. {
  921. throw new ResourceNotFoundException("Item not found.");
  922. }
  923. IEnumerable<BaseItem> themeItems = item.GetThemeVideos();
  924. while (!themeItems.Any() && request.InheritFromParent && item.GetParent() != null)
  925. {
  926. item = item.GetParent();
  927. themeItems = item.GetThemeVideos();
  928. }
  929. var dtoOptions = GetDtoOptions(_authContext, request);
  930. var items = themeItems
  931. .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
  932. .ToArray();
  933. return new ThemeMediaResult
  934. {
  935. Items = items,
  936. TotalRecordCount = items.Length,
  937. OwnerId = item.Id
  938. };
  939. }
  940. }
  941. }