ChannelManager.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209
  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. Directory.CreateDirectory(Path.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 parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
  562. var itemsResult = await GetChannelItems(channelProvider,
  563. query.User,
  564. parentItem is Channel ? null : parentItem.ExternalId,
  565. null,
  566. false,
  567. cancellationToken)
  568. .ConfigureAwait(false);
  569. if (query.ParentId == Guid.Empty)
  570. {
  571. query.Parent = channel;
  572. }
  573. query.ChannelIds = Array.Empty<Guid>();
  574. // Not yet sure why this is causing a problem
  575. query.GroupByPresentationUniqueKey = false;
  576. //_logger.LogDebug("GetChannelItemsInternal");
  577. // null if came from cache
  578. if (itemsResult != null)
  579. {
  580. var internalItems = itemsResult.Items
  581. .Select(i => GetChannelItemEntity(i, channelProvider, channel.Id, parentItem, cancellationToken))
  582. .ToArray();
  583. var existingIds = _libraryManager.GetItemIds(query);
  584. var deadIds = existingIds.Except(internalItems.Select(i => i.Id))
  585. .ToArray();
  586. foreach (var deadId in deadIds)
  587. {
  588. var deadItem = _libraryManager.GetItemById(deadId);
  589. if (deadItem != null)
  590. {
  591. _libraryManager.DeleteItem(deadItem, new DeleteOptions
  592. {
  593. DeleteFileLocation = false,
  594. DeleteFromExternalProvider = false
  595. }, parentItem, false);
  596. }
  597. }
  598. }
  599. return _libraryManager.GetItemsResult(query);
  600. }
  601. public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
  602. {
  603. var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
  604. var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, query.DtoOptions, query.User);
  605. var result = new QueryResult<BaseItemDto>
  606. {
  607. Items = returnItems,
  608. TotalRecordCount = internalResult.TotalRecordCount
  609. };
  610. return result;
  611. }
  612. private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
  613. private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
  614. User user,
  615. string externalFolderId,
  616. ChannelItemSortField? sortField,
  617. bool sortDescending,
  618. CancellationToken cancellationToken)
  619. {
  620. var userId = user == null ? null : user.Id.ToString("N");
  621. var cacheLength = CacheLength;
  622. var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
  623. try
  624. {
  625. if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
  626. {
  627. var cachedResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
  628. if (cachedResult != null)
  629. {
  630. return null;
  631. }
  632. }
  633. }
  634. catch (FileNotFoundException)
  635. {
  636. }
  637. catch (IOException)
  638. {
  639. }
  640. await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
  641. try
  642. {
  643. try
  644. {
  645. if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
  646. {
  647. var cachedResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
  648. if (cachedResult != null)
  649. {
  650. return null;
  651. }
  652. }
  653. }
  654. catch (FileNotFoundException)
  655. {
  656. }
  657. catch (IOException)
  658. {
  659. }
  660. var query = new InternalChannelItemQuery
  661. {
  662. UserId = user == null ? Guid.Empty : user.Id,
  663. SortBy = sortField,
  664. SortDescending = sortDescending,
  665. FolderId = externalFolderId
  666. };
  667. query.FolderId = externalFolderId;
  668. var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false);
  669. if (result == null)
  670. {
  671. throw new InvalidOperationException("Channel returned a null result from GetChannelItems");
  672. }
  673. CacheResponse(result, cachePath);
  674. return result;
  675. }
  676. finally
  677. {
  678. _resourcePool.Release();
  679. }
  680. }
  681. private void CacheResponse(object result, string path)
  682. {
  683. try
  684. {
  685. Directory.CreateDirectory(Path.GetDirectoryName(path));
  686. _jsonSerializer.SerializeToFile(result, path);
  687. }
  688. catch (Exception ex)
  689. {
  690. _logger.LogError(ex, "Error writing to channel cache file: {path}", path);
  691. }
  692. }
  693. private string GetChannelDataCachePath(IChannel channel,
  694. string userId,
  695. string externalFolderId,
  696. ChannelItemSortField? sortField,
  697. bool sortDescending)
  698. {
  699. var channelId = GetInternalChannelId(channel.Name).ToString("N");
  700. var userCacheKey = string.Empty;
  701. var hasCacheKey = channel as IHasCacheKey;
  702. if (hasCacheKey != null)
  703. {
  704. userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
  705. }
  706. var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N");
  707. filename += userCacheKey;
  708. var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N");
  709. if (sortField.HasValue)
  710. {
  711. filename += "-sortField-" + sortField.Value;
  712. }
  713. if (sortDescending)
  714. {
  715. filename += "-sortDescending";
  716. }
  717. filename = filename.GetMD5().ToString("N");
  718. return Path.Combine(_config.ApplicationPaths.CachePath,
  719. "channels",
  720. channelId,
  721. version,
  722. filename + ".json");
  723. }
  724. private static string GetIdToHash(string externalId, string channelName)
  725. {
  726. // Increment this as needed to force new downloads
  727. // Incorporate Name because it's being used to convert channel entity to provider
  728. return externalId + (channelName ?? string.Empty) + "16";
  729. }
  730. private T GetItemById<T>(string idString, string channelName, out bool isNew)
  731. where T : BaseItem, new()
  732. {
  733. var id = _libraryManager.GetNewItemId(GetIdToHash(idString, channelName), typeof(T));
  734. T item = null;
  735. try
  736. {
  737. item = _libraryManager.GetItemById(id) as T;
  738. }
  739. catch (Exception ex)
  740. {
  741. _logger.LogError(ex, "Error retrieving channel item from database");
  742. }
  743. if (item == null)
  744. {
  745. item = new T();
  746. isNew = true;
  747. }
  748. else
  749. {
  750. isNew = false;
  751. }
  752. item.Id = id;
  753. return item;
  754. }
  755. private BaseItem GetChannelItemEntity(ChannelItemInfo info, IChannel channelProvider, Guid internalChannelId, BaseItem parentFolder, CancellationToken cancellationToken)
  756. {
  757. var parentFolderId = parentFolder.Id;
  758. BaseItem item;
  759. bool isNew;
  760. bool forceUpdate = false;
  761. if (info.Type == ChannelItemType.Folder)
  762. {
  763. if (info.FolderType == ChannelFolderType.MusicAlbum)
  764. {
  765. item = GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew);
  766. }
  767. else if (info.FolderType == ChannelFolderType.MusicArtist)
  768. {
  769. item = GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew);
  770. }
  771. else if (info.FolderType == ChannelFolderType.PhotoAlbum)
  772. {
  773. item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew);
  774. }
  775. else if (info.FolderType == ChannelFolderType.Series)
  776. {
  777. item = GetItemById<Series>(info.Id, channelProvider.Name, out isNew);
  778. }
  779. else if (info.FolderType == ChannelFolderType.Season)
  780. {
  781. item = GetItemById<Season>(info.Id, channelProvider.Name, out isNew);
  782. }
  783. else
  784. {
  785. item = GetItemById<Folder>(info.Id, channelProvider.Name, out isNew);
  786. }
  787. }
  788. else if (info.MediaType == ChannelMediaType.Audio)
  789. {
  790. if (info.ContentType == ChannelMediaContentType.Podcast)
  791. {
  792. item = GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew);
  793. }
  794. else
  795. {
  796. item = GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
  797. }
  798. }
  799. else
  800. {
  801. if (info.ContentType == ChannelMediaContentType.Episode)
  802. {
  803. item = GetItemById<Episode>(info.Id, channelProvider.Name, out isNew);
  804. }
  805. else if (info.ContentType == ChannelMediaContentType.Movie)
  806. {
  807. item = GetItemById<Movie>(info.Id, channelProvider.Name, out isNew);
  808. }
  809. else if (info.ContentType == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer)
  810. {
  811. item = GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew);
  812. }
  813. else
  814. {
  815. item = GetItemById<Video>(info.Id, channelProvider.Name, out isNew);
  816. }
  817. }
  818. var enableMediaProbe = channelProvider is ISupportsMediaProbe;
  819. if (info.IsLiveStream)
  820. {
  821. item.RunTimeTicks = null;
  822. }
  823. else if (isNew || !enableMediaProbe)
  824. {
  825. item.RunTimeTicks = info.RunTimeTicks;
  826. }
  827. if (isNew)
  828. {
  829. item.Name = info.Name;
  830. item.Genres = info.Genres.ToArray();
  831. item.Studios = info.Studios.ToArray();
  832. item.CommunityRating = info.CommunityRating;
  833. item.Overview = info.Overview;
  834. item.IndexNumber = info.IndexNumber;
  835. item.ParentIndexNumber = info.ParentIndexNumber;
  836. item.PremiereDate = info.PremiereDate;
  837. item.ProductionYear = info.ProductionYear;
  838. item.ProviderIds = info.ProviderIds;
  839. item.OfficialRating = info.OfficialRating;
  840. item.DateCreated = info.DateCreated ?? DateTime.UtcNow;
  841. item.Tags = info.Tags.ToArray();
  842. item.OriginalTitle = info.OriginalTitle;
  843. }
  844. else if (info.Type == ChannelItemType.Folder && info.FolderType == ChannelFolderType.Container)
  845. {
  846. // At least update names of container folders
  847. if (item.Name != info.Name)
  848. {
  849. item.Name = info.Name;
  850. forceUpdate = true;
  851. }
  852. }
  853. var hasArtists = item as IHasArtist;
  854. if (hasArtists != null)
  855. {
  856. hasArtists.Artists = info.Artists.ToArray();
  857. }
  858. var hasAlbumArtists = item as IHasAlbumArtist;
  859. if (hasAlbumArtists != null)
  860. {
  861. hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
  862. }
  863. var trailer = item as Trailer;
  864. if (trailer != null)
  865. {
  866. if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
  867. {
  868. _logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
  869. forceUpdate = true;
  870. }
  871. trailer.TrailerTypes = info.TrailerTypes.ToArray();
  872. }
  873. if (info.DateModified > item.DateModified)
  874. {
  875. item.DateModified = info.DateModified;
  876. _logger.LogDebug("Forcing update due to DateModified {0}", item.Name);
  877. forceUpdate = true;
  878. }
  879. // was used for status
  880. //if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
  881. //{
  882. // item.ExternalEtag = info.Etag;
  883. // forceUpdate = true;
  884. // _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
  885. //}
  886. if (!internalChannelId.Equals(item.ChannelId))
  887. {
  888. forceUpdate = true;
  889. _logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
  890. }
  891. item.ChannelId = internalChannelId;
  892. if (!item.ParentId.Equals(parentFolderId))
  893. {
  894. forceUpdate = true;
  895. _logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
  896. }
  897. item.ParentId = parentFolderId;
  898. var hasSeries = item as IHasSeries;
  899. if (hasSeries != null)
  900. {
  901. if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
  902. {
  903. forceUpdate = true;
  904. _logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
  905. }
  906. hasSeries.SeriesName = info.SeriesName;
  907. }
  908. if (!string.Equals(item.ExternalId, info.Id, StringComparison.OrdinalIgnoreCase))
  909. {
  910. forceUpdate = true;
  911. _logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
  912. }
  913. item.ExternalId = info.Id;
  914. var channelAudioItem = item as Audio;
  915. if (channelAudioItem != null)
  916. {
  917. channelAudioItem.ExtraType = info.ExtraType;
  918. var mediaSource = info.MediaSources.FirstOrDefault();
  919. item.Path = mediaSource == null ? null : mediaSource.Path;
  920. }
  921. var channelVideoItem = item as Video;
  922. if (channelVideoItem != null)
  923. {
  924. channelVideoItem.ExtraType = info.ExtraType;
  925. var mediaSource = info.MediaSources.FirstOrDefault();
  926. item.Path = mediaSource == null ? null : mediaSource.Path;
  927. }
  928. if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
  929. {
  930. item.SetImagePath(ImageType.Primary, info.ImageUrl);
  931. _logger.LogDebug("Forcing update due to ImageUrl {0}", item.Name);
  932. forceUpdate = true;
  933. }
  934. if (!info.IsLiveStream)
  935. {
  936. if (item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
  937. {
  938. item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
  939. _logger.LogDebug("Forcing update due to Tags {0}", item.Name);
  940. forceUpdate = true;
  941. }
  942. }
  943. else
  944. {
  945. if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
  946. {
  947. item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
  948. _logger.LogDebug("Forcing update due to Tags {0}", item.Name);
  949. forceUpdate = true;
  950. }
  951. }
  952. item.OnMetadataChanged();
  953. if (isNew)
  954. {
  955. _libraryManager.CreateItem(item, parentFolder);
  956. if (info.People != null && info.People.Count > 0)
  957. {
  958. _libraryManager.UpdatePeople(item, info.People);
  959. }
  960. }
  961. else if (forceUpdate)
  962. {
  963. item.UpdateToRepository(ItemUpdateType.None, cancellationToken);
  964. }
  965. if ((isNew || forceUpdate) && info.Type == ChannelItemType.Media)
  966. {
  967. if (enableMediaProbe && !info.IsLiveStream && item.HasPathProtocol)
  968. {
  969. SaveMediaSources(item, new List<MediaSourceInfo>());
  970. }
  971. else
  972. {
  973. SaveMediaSources(item, info.MediaSources);
  974. }
  975. }
  976. if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
  977. {
  978. _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
  979. }
  980. return item;
  981. }
  982. internal IChannel GetChannelProvider(Channel channel)
  983. {
  984. if (channel == null)
  985. {
  986. throw new ArgumentNullException(nameof(channel));
  987. }
  988. var result = GetAllChannels()
  989. .FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(channel.ChannelId) || string.Equals(i.Name, channel.Name, StringComparison.OrdinalIgnoreCase));
  990. if (result == null)
  991. {
  992. throw new ResourceNotFoundException("No channel provider found for channel " + channel.Name);
  993. }
  994. return result;
  995. }
  996. internal IChannel GetChannelProvider(Guid internalChannelId)
  997. {
  998. var result = GetAllChannels()
  999. .FirstOrDefault(i => internalChannelId.Equals(GetInternalChannelId(i.Name)));
  1000. if (result == null)
  1001. {
  1002. throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId);
  1003. }
  1004. return result;
  1005. }
  1006. }
  1007. }