ChannelManager.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Common.Extensions;
  9. using MediaBrowser.Common.Net;
  10. using MediaBrowser.Common.Progress;
  11. using MediaBrowser.Controller.Channels;
  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.Providers;
  20. using MediaBrowser.Model.Channels;
  21. using MediaBrowser.Model.Dto;
  22. using MediaBrowser.Model.Entities;
  23. using MediaBrowser.Model.Globalization;
  24. using MediaBrowser.Model.IO;
  25. using MediaBrowser.Model.Querying;
  26. using MediaBrowser.Model.Serialization;
  27. using Microsoft.Extensions.Logging;
  28. namespace Emby.Server.Implementations.Channels
  29. {
  30. public class ChannelManager : IChannelManager
  31. {
  32. internal IChannel[] Channels { get; private set; }
  33. private readonly IUserManager _userManager;
  34. private readonly IUserDataManager _userDataManager;
  35. private readonly IDtoService _dtoService;
  36. private readonly ILibraryManager _libraryManager;
  37. private readonly ILogger _logger;
  38. private readonly IServerConfigurationManager _config;
  39. private readonly IFileSystem _fileSystem;
  40. private readonly IJsonSerializer _jsonSerializer;
  41. private readonly IHttpClient _httpClient;
  42. private readonly IProviderManager _providerManager;
  43. private readonly ILocalizationManager _localization;
  44. public ChannelManager(
  45. IUserManager userManager,
  46. IDtoService dtoService,
  47. ILibraryManager libraryManager,
  48. ILoggerFactory loggerFactory,
  49. IServerConfigurationManager config,
  50. IFileSystem fileSystem,
  51. IUserDataManager userDataManager,
  52. IJsonSerializer jsonSerializer,
  53. ILocalizationManager localization,
  54. IHttpClient httpClient,
  55. IProviderManager providerManager)
  56. {
  57. _userManager = userManager;
  58. _dtoService = dtoService;
  59. _libraryManager = libraryManager;
  60. _logger = loggerFactory.CreateLogger(nameof(ChannelManager));
  61. _config = config;
  62. _fileSystem = fileSystem;
  63. _userDataManager = userDataManager;
  64. _jsonSerializer = jsonSerializer;
  65. _localization = localization;
  66. _httpClient = httpClient;
  67. _providerManager = providerManager;
  68. }
  69. private static TimeSpan CacheLength => TimeSpan.FromHours(3);
  70. public void AddParts(IEnumerable<IChannel> channels)
  71. {
  72. Channels = channels.ToArray();
  73. }
  74. public bool EnableMediaSourceDisplay(BaseItem item)
  75. {
  76. var internalChannel = _libraryManager.GetItemById(item.ChannelId);
  77. var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
  78. return !(channel is IDisableMediaSourceDisplay);
  79. }
  80. public bool CanDelete(BaseItem item)
  81. {
  82. var internalChannel = _libraryManager.GetItemById(item.ChannelId);
  83. var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
  84. var supportsDelete = channel as ISupportsDelete;
  85. return supportsDelete != null && supportsDelete.CanDelete(item);
  86. }
  87. public bool EnableMediaProbe(BaseItem item)
  88. {
  89. var internalChannel = _libraryManager.GetItemById(item.ChannelId);
  90. var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
  91. return channel is ISupportsMediaProbe;
  92. }
  93. public Task DeleteItem(BaseItem item)
  94. {
  95. var internalChannel = _libraryManager.GetItemById(item.ChannelId);
  96. if (internalChannel == null)
  97. {
  98. throw new ArgumentException();
  99. }
  100. var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
  101. var supportsDelete = channel as ISupportsDelete;
  102. if (supportsDelete == null)
  103. {
  104. throw new ArgumentException();
  105. }
  106. return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
  107. }
  108. private IEnumerable<IChannel> GetAllChannels()
  109. {
  110. return Channels
  111. .OrderBy(i => i.Name);
  112. }
  113. public IEnumerable<Guid> GetInstalledChannelIds()
  114. {
  115. return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
  116. }
  117. public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
  118. {
  119. var user = query.UserId.Equals(Guid.Empty)
  120. ? null
  121. : _userManager.GetUserById(query.UserId);
  122. var channels = GetAllChannels()
  123. .Select(GetChannelEntity)
  124. .OrderBy(i => i.SortName)
  125. .ToList();
  126. if (query.IsRecordingsFolder.HasValue)
  127. {
  128. var val = query.IsRecordingsFolder.Value;
  129. channels = channels.Where(i =>
  130. {
  131. try
  132. {
  133. var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
  134. return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
  135. }
  136. catch
  137. {
  138. return false;
  139. }
  140. }).ToList();
  141. }
  142. if (query.SupportsLatestItems.HasValue)
  143. {
  144. var val = query.SupportsLatestItems.Value;
  145. channels = channels.Where(i =>
  146. {
  147. try
  148. {
  149. return GetChannelProvider(i) is ISupportsLatestMedia == val;
  150. }
  151. catch
  152. {
  153. return false;
  154. }
  155. }).ToList();
  156. }
  157. if (query.SupportsMediaDeletion.HasValue)
  158. {
  159. var val = query.SupportsMediaDeletion.Value;
  160. channels = channels.Where(i =>
  161. {
  162. try
  163. {
  164. return GetChannelProvider(i) is ISupportsDelete == val;
  165. }
  166. catch
  167. {
  168. return false;
  169. }
  170. }).ToList();
  171. }
  172. if (query.IsFavorite.HasValue)
  173. {
  174. var val = query.IsFavorite.Value;
  175. channels = channels.Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val)
  176. .ToList();
  177. }
  178. if (user != null)
  179. {
  180. channels = channels.Where(i =>
  181. {
  182. if (!i.IsVisible(user))
  183. {
  184. return false;
  185. }
  186. try
  187. {
  188. return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N"));
  189. }
  190. catch
  191. {
  192. return false;
  193. }
  194. }).ToList();
  195. }
  196. var all = channels;
  197. var totalCount = all.Count;
  198. if (query.StartIndex.HasValue)
  199. {
  200. all = all.Skip(query.StartIndex.Value).ToList();
  201. }
  202. if (query.Limit.HasValue)
  203. {
  204. all = all.Take(query.Limit.Value).ToList();
  205. }
  206. var returnItems = all.ToArray();
  207. if (query.RefreshLatestChannelItems)
  208. {
  209. foreach (var item in returnItems)
  210. {
  211. RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult();
  212. }
  213. }
  214. return new QueryResult<Channel>
  215. {
  216. Items = returnItems,
  217. TotalRecordCount = totalCount
  218. };
  219. }
  220. public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
  221. {
  222. var user = query.UserId.Equals(Guid.Empty)
  223. ? null
  224. : _userManager.GetUserById(query.UserId);
  225. var internalResult = GetChannelsInternal(query);
  226. var dtoOptions = new DtoOptions()
  227. {
  228. };
  229. //TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
  230. var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
  231. var result = new QueryResult<BaseItemDto>
  232. {
  233. Items = returnItems,
  234. TotalRecordCount = internalResult.TotalRecordCount
  235. };
  236. return result;
  237. }
  238. public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
  239. {
  240. var allChannelsList = GetAllChannels().ToList();
  241. var numComplete = 0;
  242. foreach (var channelInfo in allChannelsList)
  243. {
  244. cancellationToken.ThrowIfCancellationRequested();
  245. try
  246. {
  247. await GetChannel(channelInfo, cancellationToken).ConfigureAwait(false);
  248. }
  249. catch (OperationCanceledException)
  250. {
  251. throw;
  252. }
  253. catch (Exception ex)
  254. {
  255. _logger.LogError(ex, "Error getting channel information for {0}", channelInfo.Name);
  256. }
  257. numComplete++;
  258. double percent = (double)numComplete / allChannelsList.Count;
  259. progress.Report(100 * percent);
  260. }
  261. progress.Report(100);
  262. }
  263. private Channel GetChannelEntity(IChannel channel)
  264. {
  265. var item = GetChannel(GetInternalChannelId(channel.Name));
  266. if (item == null)
  267. {
  268. item = GetChannel(channel, CancellationToken.None).Result;
  269. }
  270. return item;
  271. }
  272. private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
  273. {
  274. var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json");
  275. try
  276. {
  277. return _jsonSerializer.DeserializeFromFile<List<MediaSourceInfo>>(path) ?? new List<MediaSourceInfo>();
  278. }
  279. catch
  280. {
  281. return new List<MediaSourceInfo>();
  282. }
  283. }
  284. private void SaveMediaSources(BaseItem item, List<MediaSourceInfo> mediaSources)
  285. {
  286. var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json");
  287. if (mediaSources == null || mediaSources.Count == 0)
  288. {
  289. try
  290. {
  291. _fileSystem.DeleteFile(path);
  292. }
  293. catch
  294. {
  295. }
  296. return;
  297. }
  298. Directory.CreateDirectory(Path.GetDirectoryName(path));
  299. _jsonSerializer.SerializeToFile(mediaSources, path);
  300. }
  301. public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
  302. {
  303. IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
  304. return results
  305. .Select(i => NormalizeMediaSource(item, i))
  306. .ToList();
  307. }
  308. public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
  309. {
  310. var channel = GetChannel(item.ChannelId);
  311. var channelPlugin = GetChannelProvider(channel);
  312. var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
  313. IEnumerable<MediaSourceInfo> results;
  314. if (requiresCallback != null)
  315. {
  316. results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
  317. .ConfigureAwait(false);
  318. }
  319. else
  320. {
  321. results = new List<MediaSourceInfo>();
  322. }
  323. return results
  324. .Select(i => NormalizeMediaSource(item, i))
  325. .ToList();
  326. }
  327. private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
  328. new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
  329. private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
  330. {
  331. if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
  332. {
  333. if ((DateTime.UtcNow - cachedInfo.Item1).TotalMinutes < 5)
  334. {
  335. return cachedInfo.Item2;
  336. }
  337. }
  338. var mediaInfo = await channel.GetChannelItemMediaInfo(id, cancellationToken)
  339. .ConfigureAwait(false);
  340. var list = mediaInfo.ToList();
  341. var item2 = new Tuple<DateTime, List<MediaSourceInfo>>(DateTime.UtcNow, list);
  342. _channelItemMediaInfo.AddOrUpdate(id, item2, (key, oldValue) => item2);
  343. return list;
  344. }
  345. private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
  346. {
  347. info.RunTimeTicks = info.RunTimeTicks ?? item.RunTimeTicks;
  348. return info;
  349. }
  350. private async Task<Channel> GetChannel(IChannel channelInfo, CancellationToken cancellationToken)
  351. {
  352. var parentFolderId = Guid.Empty;
  353. var id = GetInternalChannelId(channelInfo.Name);
  354. var path = Channel.GetInternalMetadataPath(_config.ApplicationPaths.InternalMetadataPath, id);
  355. var isNew = false;
  356. var forceUpdate = false;
  357. var item = _libraryManager.GetItemById(id) as Channel;
  358. if (item == null)
  359. {
  360. item = new Channel
  361. {
  362. Name = channelInfo.Name,
  363. Id = id,
  364. DateCreated = _fileSystem.GetCreationTimeUtc(path),
  365. DateModified = _fileSystem.GetLastWriteTimeUtc(path)
  366. };
  367. isNew = true;
  368. }
  369. if (!string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
  370. {
  371. isNew = true;
  372. }
  373. item.Path = path;
  374. if (!item.ChannelId.Equals(id))
  375. {
  376. forceUpdate = true;
  377. }
  378. item.ChannelId = id;
  379. if (item.ParentId != parentFolderId)
  380. {
  381. forceUpdate = true;
  382. }
  383. item.ParentId = parentFolderId;
  384. item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
  385. item.Overview = channelInfo.Description;
  386. if (string.IsNullOrWhiteSpace(item.Name))
  387. {
  388. item.Name = channelInfo.Name;
  389. }
  390. if (isNew)
  391. {
  392. item.OnMetadataChanged();
  393. _libraryManager.CreateItem(item, null);
  394. }
  395. await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
  396. {
  397. ForceSave = !isNew && forceUpdate
  398. }, cancellationToken);
  399. return item;
  400. }
  401. private static string GetOfficialRating(ChannelParentalRating rating)
  402. {
  403. switch (rating)
  404. {
  405. case ChannelParentalRating.Adult:
  406. return "XXX";
  407. case ChannelParentalRating.UsR:
  408. return "R";
  409. case ChannelParentalRating.UsPG13:
  410. return "PG-13";
  411. case ChannelParentalRating.UsPG:
  412. return "PG";
  413. default:
  414. return null;
  415. }
  416. }
  417. public Channel GetChannel(Guid id)
  418. {
  419. return _libraryManager.GetItemById(id) as Channel;
  420. }
  421. public Channel GetChannel(string id)
  422. {
  423. return _libraryManager.GetItemById(id) as Channel;
  424. }
  425. public ChannelFeatures[] GetAllChannelFeatures()
  426. {
  427. return _libraryManager.GetItemIds(new InternalItemsQuery
  428. {
  429. IncludeItemTypes = new[] { typeof(Channel).Name },
  430. OrderBy = new ValueTuple<string, SortOrder>[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }
  431. }).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray();
  432. }
  433. public ChannelFeatures GetChannelFeatures(string id)
  434. {
  435. if (string.IsNullOrEmpty(id))
  436. {
  437. throw new ArgumentNullException(nameof(id));
  438. }
  439. var channel = GetChannel(id);
  440. var channelProvider = GetChannelProvider(channel);
  441. return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
  442. }
  443. public bool SupportsExternalTransfer(Guid channelId)
  444. {
  445. //var channel = GetChannel(channelId);
  446. var channelProvider = GetChannelProvider(channelId);
  447. return channelProvider.GetChannelFeatures().SupportsContentDownloading;
  448. }
  449. public ChannelFeatures GetChannelFeaturesDto(Channel channel,
  450. IChannel provider,
  451. InternalChannelFeatures features)
  452. {
  453. var supportsLatest = provider is ISupportsLatestMedia;
  454. return new ChannelFeatures
  455. {
  456. CanFilter = !features.MaxPageSize.HasValue,
  457. CanSearch = provider is ISearchableChannel,
  458. ContentTypes = features.ContentTypes.ToArray(),
  459. DefaultSortFields = features.DefaultSortFields.ToArray(),
  460. MaxPageSize = features.MaxPageSize,
  461. MediaTypes = features.MediaTypes.ToArray(),
  462. SupportsSortOrderToggle = features.SupportsSortOrderToggle,
  463. SupportsLatestMedia = supportsLatest,
  464. Name = channel.Name,
  465. Id = channel.Id.ToString("N"),
  466. SupportsContentDownloading = features.SupportsContentDownloading,
  467. AutoRefreshLevels = features.AutoRefreshLevels
  468. };
  469. }
  470. private Guid GetInternalChannelId(string name)
  471. {
  472. if (string.IsNullOrEmpty(name))
  473. {
  474. throw new ArgumentNullException(nameof(name));
  475. }
  476. return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
  477. }
  478. public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
  479. {
  480. var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
  481. var items = internalResult.Items;
  482. var totalRecordCount = internalResult.TotalRecordCount;
  483. var returnItems = _dtoService.GetBaseItemDtos(items, query.DtoOptions, query.User);
  484. var result = new QueryResult<BaseItemDto>
  485. {
  486. Items = returnItems,
  487. TotalRecordCount = totalRecordCount
  488. };
  489. return result;
  490. }
  491. public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
  492. {
  493. var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
  494. if (query.ChannelIds.Length > 0)
  495. {
  496. // Avoid implicitly captured closure
  497. var ids = query.ChannelIds;
  498. channels = channels
  499. .Where(i => ids.Contains(GetInternalChannelId(i.Name)))
  500. .ToArray();
  501. }
  502. if (channels.Length == 0)
  503. {
  504. return new QueryResult<BaseItem>();
  505. }
  506. foreach (var channel in channels)
  507. {
  508. await RefreshLatestChannelItems(channel, cancellationToken).ConfigureAwait(false);
  509. }
  510. query.IsFolder = false;
  511. // hack for trailers, figure out a better way later
  512. var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
  513. if (sortByPremiereDate)
  514. {
  515. query.OrderBy = new[]
  516. {
  517. new ValueTuple<string, SortOrder>(ItemSortBy.PremiereDate, SortOrder.Descending),
  518. new ValueTuple<string, SortOrder>(ItemSortBy.ProductionYear, SortOrder.Descending),
  519. new ValueTuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending)
  520. };
  521. }
  522. else
  523. {
  524. query.OrderBy = new[]
  525. {
  526. new ValueTuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending)
  527. };
  528. }
  529. return _libraryManager.GetItemsResult(query);
  530. }
  531. private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken)
  532. {
  533. var internalChannel = await GetChannel(channel, cancellationToken);
  534. var query = new InternalItemsQuery();
  535. query.Parent = internalChannel;
  536. query.EnableTotalRecordCount = false;
  537. query.ChannelIds = new Guid[] { internalChannel.Id };
  538. var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
  539. foreach (var item in result.Items)
  540. {
  541. if (item is Folder folder)
  542. {
  543. await GetChannelItemsInternal(new InternalItemsQuery
  544. {
  545. Parent = folder,
  546. EnableTotalRecordCount = false,
  547. ChannelIds = new Guid[] { internalChannel.Id }
  548. }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
  549. }
  550. }
  551. }
  552. public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken)
  553. {
  554. // Get the internal channel entity
  555. var channel = GetChannel(query.ChannelIds[0]);
  556. // Find the corresponding channel provider plugin
  557. var channelProvider = GetChannelProvider(channel);
  558. var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
  559. var itemsResult = await GetChannelItems(channelProvider,
  560. query.User,
  561. parentItem is Channel ? null : parentItem.ExternalId,
  562. null,
  563. false,
  564. cancellationToken)
  565. .ConfigureAwait(false);
  566. if (query.ParentId == Guid.Empty)
  567. {
  568. query.Parent = channel;
  569. }
  570. query.ChannelIds = Array.Empty<Guid>();
  571. // Not yet sure why this is causing a problem
  572. query.GroupByPresentationUniqueKey = false;
  573. //_logger.LogDebug("GetChannelItemsInternal");
  574. // null if came from cache
  575. if (itemsResult != null)
  576. {
  577. var internalItems = itemsResult.Items
  578. .Select(i => GetChannelItemEntity(i, channelProvider, channel.Id, parentItem, cancellationToken))
  579. .ToArray();
  580. var existingIds = _libraryManager.GetItemIds(query);
  581. var deadIds = existingIds.Except(internalItems.Select(i => i.Id))
  582. .ToArray();
  583. foreach (var deadId in deadIds)
  584. {
  585. var deadItem = _libraryManager.GetItemById(deadId);
  586. if (deadItem != null)
  587. {
  588. _libraryManager.DeleteItem(deadItem, new DeleteOptions
  589. {
  590. DeleteFileLocation = false,
  591. DeleteFromExternalProvider = false
  592. }, parentItem, false);
  593. }
  594. }
  595. }
  596. return _libraryManager.GetItemsResult(query);
  597. }
  598. public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
  599. {
  600. var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
  601. var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, query.DtoOptions, query.User);
  602. var result = new QueryResult<BaseItemDto>
  603. {
  604. Items = returnItems,
  605. TotalRecordCount = internalResult.TotalRecordCount
  606. };
  607. return result;
  608. }
  609. private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
  610. private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
  611. User user,
  612. string externalFolderId,
  613. ChannelItemSortField? sortField,
  614. bool sortDescending,
  615. CancellationToken cancellationToken)
  616. {
  617. var userId = user == null ? null : user.Id.ToString("N");
  618. var cacheLength = CacheLength;
  619. var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
  620. try
  621. {
  622. if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
  623. {
  624. var cachedResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
  625. if (cachedResult != null)
  626. {
  627. return null;
  628. }
  629. }
  630. }
  631. catch (FileNotFoundException)
  632. {
  633. }
  634. catch (IOException)
  635. {
  636. }
  637. await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
  638. try
  639. {
  640. try
  641. {
  642. if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
  643. {
  644. var cachedResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
  645. if (cachedResult != null)
  646. {
  647. return null;
  648. }
  649. }
  650. }
  651. catch (FileNotFoundException)
  652. {
  653. }
  654. catch (IOException)
  655. {
  656. }
  657. var query = new InternalChannelItemQuery
  658. {
  659. UserId = user == null ? Guid.Empty : user.Id,
  660. SortBy = sortField,
  661. SortDescending = sortDescending,
  662. FolderId = externalFolderId
  663. };
  664. query.FolderId = externalFolderId;
  665. var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false);
  666. if (result == null)
  667. {
  668. throw new InvalidOperationException("Channel returned a null result from GetChannelItems");
  669. }
  670. CacheResponse(result, cachePath);
  671. return result;
  672. }
  673. finally
  674. {
  675. _resourcePool.Release();
  676. }
  677. }
  678. private void CacheResponse(object result, string path)
  679. {
  680. try
  681. {
  682. Directory.CreateDirectory(Path.GetDirectoryName(path));
  683. _jsonSerializer.SerializeToFile(result, path);
  684. }
  685. catch (Exception ex)
  686. {
  687. _logger.LogError(ex, "Error writing to channel cache file: {path}", path);
  688. }
  689. }
  690. private string GetChannelDataCachePath(IChannel channel,
  691. string userId,
  692. string externalFolderId,
  693. ChannelItemSortField? sortField,
  694. bool sortDescending)
  695. {
  696. var channelId = GetInternalChannelId(channel.Name).ToString("N");
  697. var userCacheKey = string.Empty;
  698. var hasCacheKey = channel as IHasCacheKey;
  699. if (hasCacheKey != null)
  700. {
  701. userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
  702. }
  703. var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N");
  704. filename += userCacheKey;
  705. var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N");
  706. if (sortField.HasValue)
  707. {
  708. filename += "-sortField-" + sortField.Value;
  709. }
  710. if (sortDescending)
  711. {
  712. filename += "-sortDescending";
  713. }
  714. filename = filename.GetMD5().ToString("N");
  715. return Path.Combine(_config.ApplicationPaths.CachePath,
  716. "channels",
  717. channelId,
  718. version,
  719. filename + ".json");
  720. }
  721. private static string GetIdToHash(string externalId, string channelName)
  722. {
  723. // Increment this as needed to force new downloads
  724. // Incorporate Name because it's being used to convert channel entity to provider
  725. return externalId + (channelName ?? string.Empty) + "16";
  726. }
  727. private T GetItemById<T>(string idString, string channelName, out bool isNew)
  728. where T : BaseItem, new()
  729. {
  730. var id = _libraryManager.GetNewItemId(GetIdToHash(idString, channelName), typeof(T));
  731. T item = null;
  732. try
  733. {
  734. item = _libraryManager.GetItemById(id) as T;
  735. }
  736. catch (Exception ex)
  737. {
  738. _logger.LogError(ex, "Error retrieving channel item from database");
  739. }
  740. if (item == null)
  741. {
  742. item = new T();
  743. isNew = true;
  744. }
  745. else
  746. {
  747. isNew = false;
  748. }
  749. item.Id = id;
  750. return item;
  751. }
  752. private BaseItem GetChannelItemEntity(ChannelItemInfo info, IChannel channelProvider, Guid internalChannelId, BaseItem parentFolder, CancellationToken cancellationToken)
  753. {
  754. var parentFolderId = parentFolder.Id;
  755. BaseItem item;
  756. bool isNew;
  757. bool forceUpdate = false;
  758. if (info.Type == ChannelItemType.Folder)
  759. {
  760. if (info.FolderType == ChannelFolderType.MusicAlbum)
  761. {
  762. item = GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew);
  763. }
  764. else if (info.FolderType == ChannelFolderType.MusicArtist)
  765. {
  766. item = GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew);
  767. }
  768. else if (info.FolderType == ChannelFolderType.PhotoAlbum)
  769. {
  770. item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew);
  771. }
  772. else if (info.FolderType == ChannelFolderType.Series)
  773. {
  774. item = GetItemById<Series>(info.Id, channelProvider.Name, out isNew);
  775. }
  776. else if (info.FolderType == ChannelFolderType.Season)
  777. {
  778. item = GetItemById<Season>(info.Id, channelProvider.Name, out isNew);
  779. }
  780. else
  781. {
  782. item = GetItemById<Folder>(info.Id, channelProvider.Name, out isNew);
  783. }
  784. }
  785. else if (info.MediaType == ChannelMediaType.Audio)
  786. {
  787. if (info.ContentType == ChannelMediaContentType.Podcast)
  788. {
  789. item = GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew);
  790. }
  791. else
  792. {
  793. item = GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
  794. }
  795. }
  796. else
  797. {
  798. if (info.ContentType == ChannelMediaContentType.Episode)
  799. {
  800. item = GetItemById<Episode>(info.Id, channelProvider.Name, out isNew);
  801. }
  802. else if (info.ContentType == ChannelMediaContentType.Movie)
  803. {
  804. item = GetItemById<Movie>(info.Id, channelProvider.Name, out isNew);
  805. }
  806. else if (info.ContentType == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer)
  807. {
  808. item = GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew);
  809. }
  810. else
  811. {
  812. item = GetItemById<Video>(info.Id, channelProvider.Name, out isNew);
  813. }
  814. }
  815. var enableMediaProbe = channelProvider is ISupportsMediaProbe;
  816. if (info.IsLiveStream)
  817. {
  818. item.RunTimeTicks = null;
  819. }
  820. else if (isNew || !enableMediaProbe)
  821. {
  822. item.RunTimeTicks = info.RunTimeTicks;
  823. }
  824. if (isNew)
  825. {
  826. item.Name = info.Name;
  827. item.Genres = info.Genres.ToArray();
  828. item.Studios = info.Studios.ToArray();
  829. item.CommunityRating = info.CommunityRating;
  830. item.Overview = info.Overview;
  831. item.IndexNumber = info.IndexNumber;
  832. item.ParentIndexNumber = info.ParentIndexNumber;
  833. item.PremiereDate = info.PremiereDate;
  834. item.ProductionYear = info.ProductionYear;
  835. item.ProviderIds = info.ProviderIds;
  836. item.OfficialRating = info.OfficialRating;
  837. item.DateCreated = info.DateCreated ?? DateTime.UtcNow;
  838. item.Tags = info.Tags.ToArray();
  839. item.OriginalTitle = info.OriginalTitle;
  840. }
  841. else if (info.Type == ChannelItemType.Folder && info.FolderType == ChannelFolderType.Container)
  842. {
  843. // At least update names of container folders
  844. if (item.Name != info.Name)
  845. {
  846. item.Name = info.Name;
  847. forceUpdate = true;
  848. }
  849. }
  850. var hasArtists = item as IHasArtist;
  851. if (hasArtists != null)
  852. {
  853. hasArtists.Artists = info.Artists.ToArray();
  854. }
  855. var hasAlbumArtists = item as IHasAlbumArtist;
  856. if (hasAlbumArtists != null)
  857. {
  858. hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
  859. }
  860. var trailer = item as Trailer;
  861. if (trailer != null)
  862. {
  863. if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
  864. {
  865. _logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
  866. forceUpdate = true;
  867. }
  868. trailer.TrailerTypes = info.TrailerTypes.ToArray();
  869. }
  870. if (info.DateModified > item.DateModified)
  871. {
  872. item.DateModified = info.DateModified;
  873. _logger.LogDebug("Forcing update due to DateModified {0}", item.Name);
  874. forceUpdate = true;
  875. }
  876. // was used for status
  877. //if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
  878. //{
  879. // item.ExternalEtag = info.Etag;
  880. // forceUpdate = true;
  881. // _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
  882. //}
  883. if (!internalChannelId.Equals(item.ChannelId))
  884. {
  885. forceUpdate = true;
  886. _logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
  887. }
  888. item.ChannelId = internalChannelId;
  889. if (!item.ParentId.Equals(parentFolderId))
  890. {
  891. forceUpdate = true;
  892. _logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
  893. }
  894. item.ParentId = parentFolderId;
  895. var hasSeries = item as IHasSeries;
  896. if (hasSeries != null)
  897. {
  898. if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
  899. {
  900. forceUpdate = true;
  901. _logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
  902. }
  903. hasSeries.SeriesName = info.SeriesName;
  904. }
  905. if (!string.Equals(item.ExternalId, info.Id, StringComparison.OrdinalIgnoreCase))
  906. {
  907. forceUpdate = true;
  908. _logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
  909. }
  910. item.ExternalId = info.Id;
  911. var channelAudioItem = item as Audio;
  912. if (channelAudioItem != null)
  913. {
  914. channelAudioItem.ExtraType = info.ExtraType;
  915. var mediaSource = info.MediaSources.FirstOrDefault();
  916. item.Path = mediaSource == null ? null : mediaSource.Path;
  917. }
  918. var channelVideoItem = item as Video;
  919. if (channelVideoItem != null)
  920. {
  921. channelVideoItem.ExtraType = info.ExtraType;
  922. var mediaSource = info.MediaSources.FirstOrDefault();
  923. item.Path = mediaSource == null ? null : mediaSource.Path;
  924. }
  925. if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
  926. {
  927. item.SetImagePath(ImageType.Primary, info.ImageUrl);
  928. _logger.LogDebug("Forcing update due to ImageUrl {0}", item.Name);
  929. forceUpdate = true;
  930. }
  931. if (!info.IsLiveStream)
  932. {
  933. if (item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
  934. {
  935. item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
  936. _logger.LogDebug("Forcing update due to Tags {0}", item.Name);
  937. forceUpdate = true;
  938. }
  939. }
  940. else
  941. {
  942. if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
  943. {
  944. item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
  945. _logger.LogDebug("Forcing update due to Tags {0}", item.Name);
  946. forceUpdate = true;
  947. }
  948. }
  949. item.OnMetadataChanged();
  950. if (isNew)
  951. {
  952. _libraryManager.CreateItem(item, parentFolder);
  953. if (info.People != null && info.People.Count > 0)
  954. {
  955. _libraryManager.UpdatePeople(item, info.People);
  956. }
  957. }
  958. else if (forceUpdate)
  959. {
  960. item.UpdateToRepository(ItemUpdateType.None, cancellationToken);
  961. }
  962. if ((isNew || forceUpdate) && info.Type == ChannelItemType.Media)
  963. {
  964. if (enableMediaProbe && !info.IsLiveStream && item.HasPathProtocol)
  965. {
  966. SaveMediaSources(item, new List<MediaSourceInfo>());
  967. }
  968. else
  969. {
  970. SaveMediaSources(item, info.MediaSources);
  971. }
  972. }
  973. if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
  974. {
  975. _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
  976. }
  977. return item;
  978. }
  979. internal IChannel GetChannelProvider(Channel channel)
  980. {
  981. if (channel == null)
  982. {
  983. throw new ArgumentNullException(nameof(channel));
  984. }
  985. var result = GetAllChannels()
  986. .FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(channel.ChannelId) || string.Equals(i.Name, channel.Name, StringComparison.OrdinalIgnoreCase));
  987. if (result == null)
  988. {
  989. throw new ResourceNotFoundException("No channel provider found for channel " + channel.Name);
  990. }
  991. return result;
  992. }
  993. internal IChannel GetChannelProvider(Guid internalChannelId)
  994. {
  995. var result = GetAllChannels()
  996. .FirstOrDefault(i => internalChannelId.Equals(GetInternalChannelId(i.Name)));
  997. if (result == null)
  998. {
  999. throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId);
  1000. }
  1001. return result;
  1002. }
  1003. }
  1004. }