ChannelManager.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  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 id = new Guid(query.ChannelId);
  181. var channel = _channelEntities.First(i => i.Id == id);
  182. var channelProvider = GetChannelProvider(channel);
  183. var items = await GetChannelItems(channelProvider, user, query.CategoryId, cancellationToken)
  184. .ConfigureAwait(false);
  185. return await GetReturnItems(items, user, query, cancellationToken).ConfigureAwait(false);
  186. }
  187. private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
  188. private async Task<IEnumerable<ChannelItemInfo>> GetChannelItems(IChannel channel, User user, string categoryId, CancellationToken cancellationToken)
  189. {
  190. var cachePath = GetChannelDataCachePath(channel, user, categoryId);
  191. try
  192. {
  193. var channelItemResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
  194. if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(channelItemResult.CacheLength) > DateTime.UtcNow)
  195. {
  196. return channelItemResult.Items;
  197. }
  198. }
  199. catch (FileNotFoundException)
  200. {
  201. }
  202. catch (DirectoryNotFoundException)
  203. {
  204. }
  205. await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
  206. try
  207. {
  208. try
  209. {
  210. var channelItemResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
  211. if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(channelItemResult.CacheLength) > DateTime.UtcNow)
  212. {
  213. return channelItemResult.Items;
  214. }
  215. }
  216. catch (FileNotFoundException)
  217. {
  218. }
  219. catch (DirectoryNotFoundException)
  220. {
  221. }
  222. var query = new InternalChannelItemQuery
  223. {
  224. User = user,
  225. CategoryId = categoryId
  226. };
  227. var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false);
  228. CacheResponse(result, cachePath);
  229. return result.Items;
  230. }
  231. finally
  232. {
  233. _resourcePool.Release();
  234. }
  235. }
  236. private void CacheResponse(ChannelItemResult result, string path)
  237. {
  238. try
  239. {
  240. Directory.CreateDirectory(Path.GetDirectoryName(path));
  241. _jsonSerializer.SerializeToFile(result, path);
  242. }
  243. catch (Exception ex)
  244. {
  245. _logger.ErrorException("Error writing to channel cache file: {0}", ex, path);
  246. }
  247. }
  248. private string GetChannelDataCachePath(IChannel channel, User user, string categoryId)
  249. {
  250. var channelId = GetInternalChannelId(channel.Name).ToString("N");
  251. var categoryKey = string.IsNullOrWhiteSpace(categoryId) ? "root" : categoryId.GetMD5().ToString("N");
  252. return Path.Combine(_config.ApplicationPaths.CachePath, channelId, categoryKey, user.Id.ToString("N") + ".json");
  253. }
  254. private async Task<QueryResult<BaseItemDto>> GetReturnItems(IEnumerable<ChannelItemInfo> items, User user, ChannelItemQuery query, CancellationToken cancellationToken)
  255. {
  256. // Get everything
  257. var fields = Enum.GetNames(typeof(ItemFields))
  258. .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
  259. .ToList();
  260. var tasks = items.Select(i => GetChannelItemEntity(i, cancellationToken));
  261. IEnumerable<BaseItem> entities = await Task.WhenAll(tasks).ConfigureAwait(false);
  262. entities = ApplyFilters(entities, query.Filters, user);
  263. entities = _libraryManager.Sort(entities, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending);
  264. var all = entities.ToList();
  265. var totalCount = all.Count;
  266. if (query.StartIndex.HasValue)
  267. {
  268. all = all.Skip(query.StartIndex.Value).ToList();
  269. }
  270. if (query.Limit.HasValue)
  271. {
  272. all = all.Take(query.Limit.Value).ToList();
  273. }
  274. await RefreshIfNeeded(all, cancellationToken).ConfigureAwait(false);
  275. var returnItemArray = all.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  276. .ToArray();
  277. return new QueryResult<BaseItemDto>
  278. {
  279. Items = returnItemArray,
  280. TotalRecordCount = totalCount
  281. };
  282. }
  283. private string GetIdToHash(string externalId)
  284. {
  285. // Increment this as needed to force new downloads
  286. return externalId + "3";
  287. }
  288. private async Task<BaseItem> GetChannelItemEntity(ChannelItemInfo info, CancellationToken cancellationToken)
  289. {
  290. BaseItem item;
  291. Guid id;
  292. var isNew = false;
  293. if (info.Type == ChannelItemType.Category)
  294. {
  295. id = GetIdToHash(info.Id).GetMBId(typeof(ChannelCategoryItem));
  296. item = _libraryManager.GetItemById(id) as ChannelCategoryItem;
  297. if (item == null)
  298. {
  299. isNew = true;
  300. item = new ChannelCategoryItem();
  301. }
  302. }
  303. else if (info.MediaType == ChannelMediaType.Audio)
  304. {
  305. id = GetIdToHash(info.Id).GetMBId(typeof(ChannelCategoryItem));
  306. item = _libraryManager.GetItemById(id) as ChannelAudioItem;
  307. if (item == null)
  308. {
  309. isNew = true;
  310. item = new ChannelAudioItem();
  311. }
  312. }
  313. else
  314. {
  315. id = GetIdToHash(info.Id).GetMBId(typeof(ChannelVideoItem));
  316. item = _libraryManager.GetItemById(id) as ChannelVideoItem;
  317. if (item == null)
  318. {
  319. isNew = true;
  320. item = new ChannelVideoItem();
  321. }
  322. }
  323. item.Id = id;
  324. item.RunTimeTicks = info.RunTimeTicks;
  325. var mediaSource = info.MediaSources.FirstOrDefault();
  326. item.Path = mediaSource == null ? null : mediaSource.Path;
  327. if (isNew)
  328. {
  329. item.Name = info.Name;
  330. item.Genres = info.Genres;
  331. item.Studios = info.Studios;
  332. item.CommunityRating = info.CommunityRating;
  333. item.OfficialRating = info.OfficialRating;
  334. item.Overview = info.Overview;
  335. item.People = info.People;
  336. item.PremiereDate = info.PremiereDate;
  337. item.ProductionYear = info.ProductionYear;
  338. item.ProviderIds = info.ProviderIds;
  339. if (info.DateCreated.HasValue)
  340. {
  341. item.DateCreated = info.DateCreated.Value;
  342. }
  343. }
  344. var channelItem = (IChannelItem)item;
  345. channelItem.OriginalImageUrl = info.ImageUrl;
  346. channelItem.ExternalId = info.Id;
  347. channelItem.ChannelItemType = info.Type;
  348. var channelMediaItem = item as IChannelMediaItem;
  349. if (channelMediaItem != null)
  350. {
  351. channelMediaItem.IsInfiniteStream = info.IsInfiniteStream;
  352. channelMediaItem.ContentType = info.ContentType;
  353. }
  354. if (isNew)
  355. {
  356. await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  357. _libraryManager.RegisterItem(item);
  358. }
  359. return item;
  360. }
  361. private async Task RefreshIfNeeded(IEnumerable<BaseItem> programs, CancellationToken cancellationToken)
  362. {
  363. foreach (var program in programs)
  364. {
  365. await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false);
  366. }
  367. }
  368. private async Task RefreshIfNeeded(BaseItem program, CancellationToken cancellationToken)
  369. {
  370. //if (_refreshedPrograms.ContainsKey(program.Id))
  371. {
  372. //return;
  373. }
  374. await program.RefreshMetadata(cancellationToken).ConfigureAwait(false);
  375. //_refreshedPrograms.TryAdd(program.Id, true);
  376. }
  377. internal IChannel GetChannelProvider(Channel channel)
  378. {
  379. return GetAllChannels().First(i => string.Equals(i.Name, channel.OriginalChannelName, StringComparison.OrdinalIgnoreCase));
  380. }
  381. private IEnumerable<BaseItem> ApplyFilters(IEnumerable<BaseItem> items, IEnumerable<ItemFilter> filters, User user)
  382. {
  383. foreach (var filter in filters.OrderByDescending(f => (int)f))
  384. {
  385. items = ApplyFilter(items, filter, user);
  386. }
  387. return items;
  388. }
  389. private IEnumerable<BaseItem> ApplyFilter(IEnumerable<BaseItem> items, ItemFilter filter, User user)
  390. {
  391. // Avoid implicitly captured closure
  392. var currentUser = user;
  393. switch (filter)
  394. {
  395. case ItemFilter.IsFavoriteOrLikes:
  396. return items.Where(item =>
  397. {
  398. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  399. if (userdata == null)
  400. {
  401. return false;
  402. }
  403. var likes = userdata.Likes ?? false;
  404. var favorite = userdata.IsFavorite;
  405. return likes || favorite;
  406. });
  407. case ItemFilter.Likes:
  408. return items.Where(item =>
  409. {
  410. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  411. return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
  412. });
  413. case ItemFilter.Dislikes:
  414. return items.Where(item =>
  415. {
  416. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  417. return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
  418. });
  419. case ItemFilter.IsFavorite:
  420. return items.Where(item =>
  421. {
  422. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  423. return userdata != null && userdata.IsFavorite;
  424. });
  425. case ItemFilter.IsResumable:
  426. return items.Where(item =>
  427. {
  428. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  429. return userdata != null && userdata.PlaybackPositionTicks > 0;
  430. });
  431. case ItemFilter.IsPlayed:
  432. return items.Where(item => item.IsPlayed(currentUser));
  433. case ItemFilter.IsUnplayed:
  434. return items.Where(item => item.IsUnplayed(currentUser));
  435. case ItemFilter.IsFolder:
  436. return items.Where(item => item.IsFolder);
  437. case ItemFilter.IsNotFolder:
  438. return items.Where(item => !item.IsFolder);
  439. }
  440. return items;
  441. }
  442. }
  443. }