LibraryService.cs 45 KB

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