ChannelManager.cs 42 KB

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