ChannelManager.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.IO;
  3. using MediaBrowser.Controller.Channels;
  4. using MediaBrowser.Controller.Configuration;
  5. using MediaBrowser.Controller.Dto;
  6. using MediaBrowser.Controller.Entities;
  7. using MediaBrowser.Controller.Library;
  8. using MediaBrowser.Controller.Providers;
  9. using MediaBrowser.Model.Channels;
  10. using MediaBrowser.Model.Dto;
  11. using MediaBrowser.Model.Entities;
  12. using MediaBrowser.Model.Logging;
  13. using MediaBrowser.Model.Querying;
  14. using MediaBrowser.Model.Serialization;
  15. using System;
  16. using System.Collections.Generic;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Threading;
  20. using System.Threading.Tasks;
  21. namespace MediaBrowser.Server.Implementations.Channels
  22. {
  23. public class ChannelManager : IChannelManager
  24. {
  25. private IChannel[] _channels;
  26. private IChannelFactory[] _factories;
  27. private List<Channel> _channelEntities = new List<Channel>();
  28. private readonly IUserManager _userManager;
  29. private readonly IUserDataManager _userDataManager;
  30. private readonly IDtoService _dtoService;
  31. private readonly ILibraryManager _libraryManager;
  32. private readonly ILogger _logger;
  33. private readonly IServerConfigurationManager _config;
  34. private readonly IFileSystem _fileSystem;
  35. private readonly IJsonSerializer _jsonSerializer;
  36. public ChannelManager(IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer)
  37. {
  38. _userManager = userManager;
  39. _dtoService = dtoService;
  40. _libraryManager = libraryManager;
  41. _logger = logger;
  42. _config = config;
  43. _fileSystem = fileSystem;
  44. _userDataManager = userDataManager;
  45. _jsonSerializer = jsonSerializer;
  46. }
  47. public void AddParts(IEnumerable<IChannel> channels, IEnumerable<IChannelFactory> factories)
  48. {
  49. _channels = channels.ToArray();
  50. _factories = factories.ToArray();
  51. }
  52. private IEnumerable<IChannel> GetAllChannels()
  53. {
  54. return _factories
  55. .SelectMany(i =>
  56. {
  57. try
  58. {
  59. return i.GetChannels().ToList();
  60. }
  61. catch (Exception ex)
  62. {
  63. _logger.ErrorException("Error getting channel list", ex);
  64. return new List<IChannel>();
  65. }
  66. })
  67. .Concat(_channels)
  68. .OrderBy(i => i.Name);
  69. }
  70. public Task<QueryResult<BaseItemDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken)
  71. {
  72. var user = string.IsNullOrWhiteSpace(query.UserId)
  73. ? null
  74. : _userManager.GetUserById(new Guid(query.UserId));
  75. var channels = _channelEntities.OrderBy(i => i.SortName).ToList();
  76. if (user != null)
  77. {
  78. channels = channels.Where(i => GetChannelProvider(i).IsEnabledFor(user) && i.IsVisible(user))
  79. .ToList();
  80. }
  81. // Get everything
  82. var fields = Enum.GetNames(typeof(ItemFields))
  83. .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
  84. .ToList();
  85. var returnItems = channels.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  86. .ToArray();
  87. var result = new QueryResult<BaseItemDto>
  88. {
  89. Items = returnItems,
  90. TotalRecordCount = returnItems.Length
  91. };
  92. return Task.FromResult(result);
  93. }
  94. public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
  95. {
  96. var allChannelsList = GetAllChannels().ToList();
  97. var list = new List<Channel>();
  98. var numComplete = 0;
  99. foreach (var channelInfo in allChannelsList)
  100. {
  101. cancellationToken.ThrowIfCancellationRequested();
  102. try
  103. {
  104. var item = await GetChannel(channelInfo, cancellationToken).ConfigureAwait(false);
  105. list.Add(item);
  106. _libraryManager.RegisterItem(item);
  107. }
  108. catch (OperationCanceledException)
  109. {
  110. throw;
  111. }
  112. catch (Exception ex)
  113. {
  114. _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Name);
  115. }
  116. numComplete++;
  117. double percent = numComplete;
  118. percent /= allChannelsList.Count;
  119. progress.Report(100 * percent);
  120. }
  121. _channelEntities = list.ToList();
  122. progress.Report(100);
  123. }
  124. private async Task<Channel> GetChannel(IChannel channelInfo, CancellationToken cancellationToken)
  125. {
  126. var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(channelInfo.Name));
  127. var fileInfo = new DirectoryInfo(path);
  128. var isNew = false;
  129. if (!fileInfo.Exists)
  130. {
  131. _logger.Debug("Creating directory {0}", path);
  132. Directory.CreateDirectory(path);
  133. fileInfo = new DirectoryInfo(path);
  134. if (!fileInfo.Exists)
  135. {
  136. throw new IOException("Path not created: " + path);
  137. }
  138. isNew = true;
  139. }
  140. var id = GetInternalChannelId(channelInfo.Name);
  141. var item = _libraryManager.GetItemById(id) as Channel;
  142. if (item == null)
  143. {
  144. item = new Channel
  145. {
  146. Name = channelInfo.Name,
  147. Id = id,
  148. DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo),
  149. DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo),
  150. Path = path
  151. };
  152. isNew = true;
  153. }
  154. var info = channelInfo.GetChannelInfo();
  155. item.HomePageUrl = info.HomePageUrl;
  156. item.OriginalChannelName = channelInfo.Name;
  157. if (string.IsNullOrEmpty(item.Name))
  158. {
  159. item.Name = channelInfo.Name;
  160. }
  161. await item.RefreshMetadata(new MetadataRefreshOptions
  162. {
  163. ForceSave = isNew
  164. }, cancellationToken);
  165. return item;
  166. }
  167. private Guid GetInternalChannelId(string name)
  168. {
  169. if (string.IsNullOrWhiteSpace(name))
  170. {
  171. throw new ArgumentNullException("name");
  172. }
  173. return ("Channel " + name).GetMBId(typeof(Channel));
  174. }
  175. public async Task<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken)
  176. {
  177. var user = string.IsNullOrWhiteSpace(query.UserId)
  178. ? null
  179. : _userManager.GetUserById(new Guid(query.UserId));
  180. var queryChannelId = query.ChannelId;
  181. var channels = string.IsNullOrWhiteSpace(queryChannelId)
  182. ? _channelEntities
  183. : _channelEntities.Where(i => i.Id == new Guid(queryChannelId));
  184. var itemTasks = channels.Select(async channel =>
  185. {
  186. var channelProvider = GetChannelProvider(channel);
  187. var items = await GetChannelItems(channelProvider, user, query.CategoryId, cancellationToken)
  188. .ConfigureAwait(false);
  189. var channelId = channel.Id.ToString("N");
  190. var tasks = items.Select(i => GetChannelItemEntity(i, channelId, cancellationToken));
  191. return await Task.WhenAll(tasks).ConfigureAwait(false);
  192. });
  193. var results = await Task.WhenAll(itemTasks).ConfigureAwait(false);
  194. return await GetReturnItems(results.SelectMany(i => i), user, query, cancellationToken).ConfigureAwait(false);
  195. }
  196. private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
  197. private async Task<IEnumerable<ChannelItemInfo>> GetChannelItems(IChannel channel, User user, string categoryId, CancellationToken cancellationToken)
  198. {
  199. var cachePath = GetChannelDataCachePath(channel, user, categoryId);
  200. try
  201. {
  202. var channelItemResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
  203. if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(channelItemResult.CacheLength) > DateTime.UtcNow)
  204. {
  205. return channelItemResult.Items;
  206. }
  207. }
  208. catch (FileNotFoundException)
  209. {
  210. }
  211. catch (DirectoryNotFoundException)
  212. {
  213. }
  214. await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
  215. try
  216. {
  217. try
  218. {
  219. var channelItemResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
  220. if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(channelItemResult.CacheLength) > DateTime.UtcNow)
  221. {
  222. return channelItemResult.Items;
  223. }
  224. }
  225. catch (FileNotFoundException)
  226. {
  227. }
  228. catch (DirectoryNotFoundException)
  229. {
  230. }
  231. var query = new InternalChannelItemQuery
  232. {
  233. User = user,
  234. CategoryId = categoryId
  235. };
  236. var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false);
  237. CacheResponse(result, cachePath);
  238. return result.Items;
  239. }
  240. finally
  241. {
  242. _resourcePool.Release();
  243. }
  244. }
  245. private void CacheResponse(ChannelItemResult result, string path)
  246. {
  247. try
  248. {
  249. Directory.CreateDirectory(Path.GetDirectoryName(path));
  250. _jsonSerializer.SerializeToFile(result, path);
  251. }
  252. catch (Exception ex)
  253. {
  254. _logger.ErrorException("Error writing to channel cache file: {0}", ex, path);
  255. }
  256. }
  257. private string GetChannelDataCachePath(IChannel channel, User user, string categoryId)
  258. {
  259. var channelId = GetInternalChannelId(channel.Name).ToString("N");
  260. var categoryKey = string.IsNullOrWhiteSpace(categoryId) ? "root" : categoryId.GetMD5().ToString("N");
  261. return Path.Combine(_config.ApplicationPaths.CachePath, channelId, categoryKey, user.Id.ToString("N") + ".json");
  262. }
  263. private async Task<QueryResult<BaseItemDto>> GetReturnItems(IEnumerable<BaseItem> items, User user, ChannelItemQuery query, CancellationToken cancellationToken)
  264. {
  265. items = ApplyFilters(items, query.Filters, user);
  266. items = _libraryManager.Sort(items, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending);
  267. var all = items.ToList();
  268. var totalCount = all.Count;
  269. if (query.StartIndex.HasValue)
  270. {
  271. all = all.Skip(query.StartIndex.Value).ToList();
  272. }
  273. if (query.Limit.HasValue)
  274. {
  275. all = all.Take(query.Limit.Value).ToList();
  276. }
  277. await RefreshIfNeeded(all, cancellationToken).ConfigureAwait(false);
  278. // Get everything
  279. var fields = Enum.GetNames(typeof(ItemFields))
  280. .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
  281. .ToList();
  282. var returnItemArray = all.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  283. .ToArray();
  284. return new QueryResult<BaseItemDto>
  285. {
  286. Items = returnItemArray,
  287. TotalRecordCount = totalCount
  288. };
  289. }
  290. private string GetIdToHash(string externalId)
  291. {
  292. // Increment this as needed to force new downloads
  293. return externalId + "4";
  294. }
  295. private async Task<BaseItem> GetChannelItemEntity(ChannelItemInfo info, string internalChannnelId, CancellationToken cancellationToken)
  296. {
  297. BaseItem item;
  298. Guid id;
  299. var isNew = false;
  300. if (info.Type == ChannelItemType.Category)
  301. {
  302. id = GetIdToHash(info.Id).GetMBId(typeof(ChannelCategoryItem));
  303. item = _libraryManager.GetItemById(id) as ChannelCategoryItem;
  304. if (item == null)
  305. {
  306. isNew = true;
  307. item = new ChannelCategoryItem();
  308. }
  309. }
  310. else if (info.MediaType == ChannelMediaType.Audio)
  311. {
  312. id = GetIdToHash(info.Id).GetMBId(typeof(ChannelCategoryItem));
  313. item = _libraryManager.GetItemById(id) as ChannelAudioItem;
  314. if (item == null)
  315. {
  316. isNew = true;
  317. item = new ChannelAudioItem();
  318. }
  319. }
  320. else
  321. {
  322. id = GetIdToHash(info.Id).GetMBId(typeof(ChannelVideoItem));
  323. item = _libraryManager.GetItemById(id) as ChannelVideoItem;
  324. if (item == null)
  325. {
  326. isNew = true;
  327. item = new ChannelVideoItem();
  328. }
  329. }
  330. item.Id = id;
  331. item.RunTimeTicks = info.RunTimeTicks;
  332. var mediaSource = info.MediaSources.FirstOrDefault();
  333. item.Path = mediaSource == null ? null : mediaSource.Path;
  334. if (isNew)
  335. {
  336. item.Name = info.Name;
  337. item.Genres = info.Genres;
  338. item.Studios = info.Studios;
  339. item.CommunityRating = info.CommunityRating;
  340. item.OfficialRating = info.OfficialRating;
  341. item.Overview = info.Overview;
  342. item.People = info.People;
  343. item.PremiereDate = info.PremiereDate;
  344. item.ProductionYear = info.ProductionYear;
  345. item.ProviderIds = info.ProviderIds;
  346. if (info.DateCreated.HasValue)
  347. {
  348. item.DateCreated = info.DateCreated.Value;
  349. }
  350. }
  351. var channelItem = (IChannelItem)item;
  352. channelItem.OriginalImageUrl = info.ImageUrl;
  353. channelItem.ExternalId = info.Id;
  354. channelItem.ChannelId = internalChannnelId;
  355. channelItem.ChannelItemType = info.Type;
  356. var channelMediaItem = item as IChannelMediaItem;
  357. if (channelMediaItem != null)
  358. {
  359. channelMediaItem.IsInfiniteStream = info.IsInfiniteStream;
  360. channelMediaItem.ContentType = info.ContentType;
  361. }
  362. if (isNew)
  363. {
  364. await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  365. _libraryManager.RegisterItem(item);
  366. }
  367. return item;
  368. }
  369. private async Task RefreshIfNeeded(IEnumerable<BaseItem> programs, CancellationToken cancellationToken)
  370. {
  371. foreach (var program in programs)
  372. {
  373. await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false);
  374. }
  375. }
  376. private async Task RefreshIfNeeded(BaseItem program, CancellationToken cancellationToken)
  377. {
  378. //if (_refreshedPrograms.ContainsKey(program.Id))
  379. {
  380. //return;
  381. }
  382. await program.RefreshMetadata(cancellationToken).ConfigureAwait(false);
  383. //_refreshedPrograms.TryAdd(program.Id, true);
  384. }
  385. internal IChannel GetChannelProvider(Channel channel)
  386. {
  387. return GetAllChannels().First(i => string.Equals(i.Name, channel.OriginalChannelName, StringComparison.OrdinalIgnoreCase));
  388. }
  389. private IEnumerable<BaseItem> ApplyFilters(IEnumerable<BaseItem> items, IEnumerable<ItemFilter> filters, User user)
  390. {
  391. foreach (var filter in filters.OrderByDescending(f => (int)f))
  392. {
  393. items = ApplyFilter(items, filter, user);
  394. }
  395. return items;
  396. }
  397. private IEnumerable<BaseItem> ApplyFilter(IEnumerable<BaseItem> items, ItemFilter filter, User user)
  398. {
  399. // Avoid implicitly captured closure
  400. var currentUser = user;
  401. switch (filter)
  402. {
  403. case ItemFilter.IsFavoriteOrLikes:
  404. return items.Where(item =>
  405. {
  406. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  407. if (userdata == null)
  408. {
  409. return false;
  410. }
  411. var likes = userdata.Likes ?? false;
  412. var favorite = userdata.IsFavorite;
  413. return likes || favorite;
  414. });
  415. case ItemFilter.Likes:
  416. return items.Where(item =>
  417. {
  418. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  419. return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
  420. });
  421. case ItemFilter.Dislikes:
  422. return items.Where(item =>
  423. {
  424. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  425. return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
  426. });
  427. case ItemFilter.IsFavorite:
  428. return items.Where(item =>
  429. {
  430. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  431. return userdata != null && userdata.IsFavorite;
  432. });
  433. case ItemFilter.IsResumable:
  434. return items.Where(item =>
  435. {
  436. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  437. return userdata != null && userdata.PlaybackPositionTicks > 0;
  438. });
  439. case ItemFilter.IsPlayed:
  440. return items.Where(item => item.IsPlayed(currentUser));
  441. case ItemFilter.IsUnplayed:
  442. return items.Where(item => item.IsUnplayed(currentUser));
  443. case ItemFilter.IsFolder:
  444. return items.Where(item => item.IsFolder);
  445. case ItemFilter.IsNotFolder:
  446. return items.Where(item => !item.IsFolder);
  447. }
  448. return items;
  449. }
  450. }
  451. }