LiveTvManager.cs 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.IO;
  3. using MediaBrowser.Common.ScheduledTasks;
  4. using MediaBrowser.Controller.Configuration;
  5. using MediaBrowser.Controller.Drawing;
  6. using MediaBrowser.Controller.Dto;
  7. using MediaBrowser.Controller.Entities;
  8. using MediaBrowser.Controller.Library;
  9. using MediaBrowser.Controller.LiveTv;
  10. using MediaBrowser.Controller.Persistence;
  11. using MediaBrowser.Controller.Providers;
  12. using MediaBrowser.Controller.Sorting;
  13. using MediaBrowser.Model.Entities;
  14. using MediaBrowser.Model.LiveTv;
  15. using MediaBrowser.Model.Logging;
  16. using MediaBrowser.Model.Querying;
  17. using System;
  18. using System.Collections.Concurrent;
  19. using System.Collections.Generic;
  20. using System.IO;
  21. using System.Linq;
  22. using System.Threading;
  23. using System.Threading.Tasks;
  24. namespace MediaBrowser.Server.Implementations.LiveTv
  25. {
  26. /// <summary>
  27. /// Class LiveTvManager
  28. /// </summary>
  29. public class LiveTvManager : ILiveTvManager, IDisposable
  30. {
  31. private readonly IServerConfigurationManager _config;
  32. private readonly IFileSystem _fileSystem;
  33. private readonly ILogger _logger;
  34. private readonly IItemRepository _itemRepo;
  35. private readonly IUserManager _userManager;
  36. private readonly IUserDataManager _userDataManager;
  37. private readonly ILibraryManager _libraryManager;
  38. private readonly ITaskManager _taskManager;
  39. private readonly LiveTvDtoService _tvDtoService;
  40. private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
  41. private readonly ConcurrentDictionary<string, LiveStreamInfo> _openStreams =
  42. new ConcurrentDictionary<string, LiveStreamInfo>();
  43. private List<Guid> _channelIdList = new List<Guid>();
  44. private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>();
  45. private readonly ConcurrentDictionary<Guid, bool> _refreshedPrograms = new ConcurrentDictionary<Guid, bool>();
  46. private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1);
  47. public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager)
  48. {
  49. _config = config;
  50. _fileSystem = fileSystem;
  51. _logger = logger;
  52. _itemRepo = itemRepo;
  53. _userManager = userManager;
  54. _libraryManager = libraryManager;
  55. _taskManager = taskManager;
  56. _userDataManager = userDataManager;
  57. _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, _itemRepo);
  58. }
  59. /// <summary>
  60. /// Gets the services.
  61. /// </summary>
  62. /// <value>The services.</value>
  63. public IReadOnlyList<ILiveTvService> Services
  64. {
  65. get { return _services; }
  66. }
  67. public ILiveTvService ActiveService { get; private set; }
  68. /// <summary>
  69. /// Adds the parts.
  70. /// </summary>
  71. /// <param name="services">The services.</param>
  72. public void AddParts(IEnumerable<ILiveTvService> services)
  73. {
  74. _services.AddRange(services);
  75. SetActiveService(_services.FirstOrDefault());
  76. }
  77. private void SetActiveService(ILiveTvService service)
  78. {
  79. if (ActiveService != null)
  80. {
  81. ActiveService.DataSourceChanged -= service_DataSourceChanged;
  82. }
  83. ActiveService = service;
  84. if (service != null)
  85. {
  86. service.DataSourceChanged += service_DataSourceChanged;
  87. }
  88. }
  89. void service_DataSourceChanged(object sender, EventArgs e)
  90. {
  91. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  92. }
  93. public async Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken)
  94. {
  95. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
  96. var channels = _channelIdList.Select(_libraryManager.GetItemById)
  97. .Where(i => i != null)
  98. .OfType<LiveTvChannel>();
  99. if (user != null)
  100. {
  101. // Avoid implicitly captured closure
  102. var currentUser = user;
  103. channels = channels
  104. .Where(i => i.IsParentalAllowed(currentUser))
  105. .OrderBy(i =>
  106. {
  107. double number = 0;
  108. if (!string.IsNullOrEmpty(i.Number))
  109. {
  110. double.TryParse(i.Number, out number);
  111. }
  112. return number;
  113. });
  114. }
  115. channels = channels.OrderBy(i =>
  116. {
  117. double number = 0;
  118. if (!string.IsNullOrEmpty(i.Number))
  119. {
  120. double.TryParse(i.Number, out number);
  121. }
  122. return number;
  123. }).ThenBy(i => i.Name);
  124. var allChannels = channels.ToList();
  125. IEnumerable<LiveTvChannel> allEnumerable = allChannels;
  126. if (query.StartIndex.HasValue)
  127. {
  128. allEnumerable = allEnumerable.Skip(query.StartIndex.Value);
  129. }
  130. if (query.Limit.HasValue)
  131. {
  132. allEnumerable = allEnumerable.Take(query.Limit.Value);
  133. }
  134. var returnList = new List<ChannelInfoDto>();
  135. foreach (var channel in allEnumerable)
  136. {
  137. var currentProgram = await GetCurrentProgram(channel.ExternalId, cancellationToken).ConfigureAwait(false);
  138. returnList.Add(_tvDtoService.GetChannelInfoDto(channel, currentProgram, user));
  139. }
  140. var result = new QueryResult<ChannelInfoDto>
  141. {
  142. Items = returnList.ToArray(),
  143. TotalRecordCount = allChannels.Count
  144. };
  145. return result;
  146. }
  147. public LiveTvChannel GetInternalChannel(string id)
  148. {
  149. return GetInternalChannel(new Guid(id));
  150. }
  151. private LiveTvChannel GetInternalChannel(Guid id)
  152. {
  153. return _libraryManager.GetItemById(id) as LiveTvChannel;
  154. }
  155. public async Task<LiveTvProgram> GetInternalProgram(string id, CancellationToken cancellationToken)
  156. {
  157. var guid = new Guid(id);
  158. LiveTvProgram obj = null;
  159. _programs.TryGetValue(guid, out obj);
  160. if (obj != null)
  161. {
  162. await RefreshIfNeeded(obj, cancellationToken).ConfigureAwait(false);
  163. }
  164. return obj;
  165. }
  166. private async Task RefreshIfNeeded(IEnumerable<LiveTvProgram> programs, CancellationToken cancellationToken)
  167. {
  168. foreach (var program in programs)
  169. {
  170. await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false);
  171. }
  172. }
  173. private async Task RefreshIfNeeded(LiveTvProgram program, CancellationToken cancellationToken)
  174. {
  175. if (_refreshedPrograms.ContainsKey(program.Id))
  176. {
  177. return;
  178. }
  179. await program.RefreshMetadata(CancellationToken.None).ConfigureAwait(false);
  180. _refreshedPrograms.TryAdd(program.Id, true);
  181. }
  182. public async Task<ILiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken)
  183. {
  184. var service = ActiveService;
  185. var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
  186. var recording = recordings.FirstOrDefault(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
  187. return await GetRecording(recording, service.Name, cancellationToken).ConfigureAwait(false);
  188. }
  189. private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
  190. public async Task<LiveStreamInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
  191. {
  192. await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  193. try
  194. {
  195. var service = ActiveService;
  196. var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
  197. var recording = recordings.First(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
  198. var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
  199. if (!string.IsNullOrEmpty(result.Id))
  200. {
  201. _openStreams.AddOrUpdate(result.Id, result, (key, info) => result);
  202. }
  203. return result;
  204. }
  205. catch (Exception ex)
  206. {
  207. _logger.ErrorException("Error getting recording stream", ex);
  208. throw;
  209. }
  210. finally
  211. {
  212. _liveStreamSemaphore.Release();
  213. }
  214. }
  215. public async Task<LiveStreamInfo> GetChannelStream(string id, CancellationToken cancellationToken)
  216. {
  217. await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  218. try
  219. {
  220. var service = ActiveService;
  221. var channel = GetInternalChannel(id);
  222. _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
  223. var result = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false);
  224. if (!string.IsNullOrEmpty(result.Id))
  225. {
  226. _openStreams.AddOrUpdate(result.Id, result, (key, info) => result);
  227. }
  228. return result;
  229. }
  230. catch (Exception ex)
  231. {
  232. _logger.ErrorException("Error getting channel stream", ex);
  233. throw;
  234. }
  235. finally
  236. {
  237. _liveStreamSemaphore.Release();
  238. }
  239. }
  240. private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
  241. {
  242. var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "tvchannels", _fileSystem.GetValidFilename(channelInfo.Name));
  243. var fileInfo = new DirectoryInfo(path);
  244. var isNew = false;
  245. if (!fileInfo.Exists)
  246. {
  247. _logger.Debug("Creating directory {0}", path);
  248. Directory.CreateDirectory(path);
  249. fileInfo = new DirectoryInfo(path);
  250. if (!fileInfo.Exists)
  251. {
  252. throw new IOException("Path not created: " + path);
  253. }
  254. isNew = true;
  255. }
  256. var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
  257. var item = _itemRepo.RetrieveItem(id) as LiveTvChannel;
  258. if (item == null)
  259. {
  260. item = new LiveTvChannel
  261. {
  262. Name = channelInfo.Name,
  263. Id = id,
  264. DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo),
  265. DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo),
  266. Path = path
  267. };
  268. isNew = true;
  269. }
  270. item.ChannelType = channelInfo.ChannelType;
  271. item.ProviderImageUrl = channelInfo.ImageUrl;
  272. item.HasProviderImage = channelInfo.HasImage;
  273. item.ProviderImagePath = channelInfo.ImagePath;
  274. item.ExternalId = channelInfo.Id;
  275. item.ServiceName = serviceName;
  276. item.Number = channelInfo.Number;
  277. if (string.IsNullOrEmpty(item.Name))
  278. {
  279. item.Name = channelInfo.Name;
  280. }
  281. await item.RefreshMetadata(new MetadataRefreshOptions
  282. {
  283. ForceSave = isNew
  284. }, cancellationToken);
  285. return item;
  286. }
  287. private LiveTvProgram GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
  288. {
  289. var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
  290. var item = _itemRepo.RetrieveItem(id) as LiveTvProgram;
  291. if (item == null)
  292. {
  293. item = new LiveTvProgram
  294. {
  295. Name = info.Name,
  296. Id = id,
  297. DateCreated = DateTime.UtcNow,
  298. DateModified = DateTime.UtcNow
  299. };
  300. }
  301. item.ChannelType = channelType;
  302. item.ServiceName = serviceName;
  303. item.Audio = info.Audio;
  304. item.ExternalChannelId = info.ChannelId;
  305. item.CommunityRating = info.CommunityRating;
  306. item.EndDate = info.EndDate;
  307. item.EpisodeTitle = info.EpisodeTitle;
  308. item.ExternalId = info.Id;
  309. item.Genres = info.Genres;
  310. item.HasProviderImage = info.HasImage;
  311. item.IsHD = info.IsHD;
  312. item.IsKids = info.IsKids;
  313. item.IsLive = info.IsLive;
  314. item.IsMovie = info.IsMovie;
  315. item.IsNews = info.IsNews;
  316. item.IsPremiere = info.IsPremiere;
  317. item.IsRepeat = info.IsRepeat;
  318. item.IsSeries = info.IsSeries;
  319. item.IsSports = info.IsSports;
  320. item.Name = info.Name;
  321. item.OfficialRating = info.OfficialRating;
  322. item.Overview = info.Overview;
  323. item.PremiereDate = info.OriginalAirDate;
  324. item.ProviderImagePath = info.ImagePath;
  325. item.ProviderImageUrl = info.ImageUrl;
  326. item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
  327. item.StartDate = info.StartDate;
  328. return item;
  329. }
  330. private async Task<ILiveTvRecording> GetRecording(RecordingInfo info, string serviceName, CancellationToken cancellationToken)
  331. {
  332. var isNew = false;
  333. var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id);
  334. var item = _itemRepo.RetrieveItem(id) as ILiveTvRecording;
  335. if (item == null)
  336. {
  337. if (info.ChannelType == ChannelType.TV)
  338. {
  339. item = new LiveTvVideoRecording
  340. {
  341. Name = info.Name,
  342. Id = id,
  343. DateCreated = DateTime.UtcNow,
  344. DateModified = DateTime.UtcNow,
  345. VideoType = VideoType.VideoFile
  346. };
  347. }
  348. else
  349. {
  350. item = new LiveTvAudioRecording
  351. {
  352. Name = info.Name,
  353. Id = id,
  354. DateCreated = DateTime.UtcNow,
  355. DateModified = DateTime.UtcNow
  356. };
  357. }
  358. if (!string.IsNullOrEmpty(info.Path))
  359. {
  360. item.Path = info.Path;
  361. }
  362. else if (!string.IsNullOrEmpty(info.Url))
  363. {
  364. item.Path = info.Url;
  365. }
  366. isNew = true;
  367. }
  368. item.RecordingInfo = info;
  369. item.ServiceName = serviceName;
  370. await item.RefreshMetadata(new MetadataRefreshOptions
  371. {
  372. ForceSave = isNew
  373. }, cancellationToken);
  374. _libraryManager.RegisterItem((BaseItem)item);
  375. return item;
  376. }
  377. private LiveTvChannel GetChannel(LiveTvProgram program)
  378. {
  379. var programChannelId = program.ExternalChannelId;
  380. var internalProgramChannelId = _tvDtoService.GetInternalChannelId(program.ServiceName, programChannelId);
  381. return GetInternalChannel(internalProgramChannelId);
  382. }
  383. public async Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
  384. {
  385. var program = await GetInternalProgram(id, cancellationToken).ConfigureAwait(false);
  386. var channel = GetChannel(program);
  387. var dto = _tvDtoService.GetProgramInfoDto(program, channel, user);
  388. await AddRecordingInfo(new[] { dto }, cancellationToken).ConfigureAwait(false);
  389. return dto;
  390. }
  391. public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
  392. {
  393. IEnumerable<LiveTvProgram> programs = _programs.Values;
  394. if (query.MinEndDate.HasValue)
  395. {
  396. var val = query.MinEndDate.Value;
  397. programs = programs.Where(i => i.EndDate.HasValue && i.EndDate.Value >= val);
  398. }
  399. if (query.MinStartDate.HasValue)
  400. {
  401. var val = query.MinStartDate.Value;
  402. programs = programs.Where(i => i.StartDate >= val);
  403. }
  404. if (query.MaxEndDate.HasValue)
  405. {
  406. var val = query.MaxEndDate.Value;
  407. programs = programs.Where(i => i.EndDate.HasValue && i.EndDate.Value <= val);
  408. }
  409. if (query.MaxStartDate.HasValue)
  410. {
  411. var val = query.MaxStartDate.Value;
  412. programs = programs.Where(i => i.StartDate <= val);
  413. }
  414. if (query.ChannelIdList.Length > 0)
  415. {
  416. var guids = query.ChannelIdList.Select(i => new Guid(i)).ToList();
  417. var serviceName = ActiveService.Name;
  418. programs = programs.Where(i =>
  419. {
  420. var programChannelId = i.ExternalChannelId;
  421. var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId);
  422. return guids.Contains(internalProgramChannelId);
  423. });
  424. }
  425. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
  426. if (user != null)
  427. {
  428. // Avoid implicitly captured closure
  429. var currentUser = user;
  430. programs = programs.Where(i => i.IsParentalAllowed(currentUser));
  431. }
  432. var programList = programs.ToList();
  433. var returnArray = programList
  434. .Select(i =>
  435. {
  436. var channel = GetChannel(i);
  437. return _tvDtoService.GetProgramInfoDto(i, channel, user);
  438. })
  439. .ToArray();
  440. await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false);
  441. await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
  442. var result = new QueryResult<ProgramInfoDto>
  443. {
  444. Items = returnArray,
  445. TotalRecordCount = returnArray.Length
  446. };
  447. return result;
  448. }
  449. public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
  450. {
  451. IEnumerable<LiveTvProgram> programs = _programs.Values;
  452. var user = _userManager.GetUserById(new Guid(query.UserId));
  453. // Avoid implicitly captured closure
  454. var currentUser = user;
  455. programs = programs.Where(i => i.IsParentalAllowed(currentUser));
  456. if (query.IsAiring.HasValue)
  457. {
  458. var val = query.IsAiring.Value;
  459. programs = programs.Where(i => i.IsAiring == val);
  460. }
  461. if (query.HasAired.HasValue)
  462. {
  463. var val = query.HasAired.Value;
  464. programs = programs.Where(i => i.HasAired == val);
  465. }
  466. var serviceName = ActiveService.Name;
  467. var programList = programs.ToList();
  468. var genres = programList.SelectMany(i => i.Genres)
  469. .Distinct(StringComparer.OrdinalIgnoreCase)
  470. .Select(i => _libraryManager.GetGenre(i))
  471. .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
  472. programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1)
  473. .ThenByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres))
  474. .ThenBy(i => i.StartDate);
  475. if (query.Limit.HasValue)
  476. {
  477. programs = programs.Take(query.Limit.Value)
  478. .OrderBy(i => i.StartDate);
  479. }
  480. programList = programs.ToList();
  481. await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false);
  482. var returnArray = programList
  483. .Select(i =>
  484. {
  485. var channel = GetChannel(i);
  486. return _tvDtoService.GetProgramInfoDto(i, channel, user);
  487. })
  488. .ToArray();
  489. await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
  490. var result = new QueryResult<ProgramInfoDto>
  491. {
  492. Items = returnArray,
  493. TotalRecordCount = returnArray.Length
  494. };
  495. return result;
  496. }
  497. private int GetRecommendationScore(LiveTvProgram program, Guid userId, string serviceName, Dictionary<string, Genre> genres)
  498. {
  499. var score = 0;
  500. if (program.IsLive)
  501. {
  502. score++;
  503. }
  504. if (program.IsSeries && !program.IsRepeat)
  505. {
  506. score++;
  507. }
  508. var internalChannelId = _tvDtoService.GetInternalChannelId(serviceName, program.ExternalChannelId);
  509. var channel = GetInternalChannel(internalChannelId);
  510. var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey());
  511. if ((channelUserdata.Likes ?? false))
  512. {
  513. score += 2;
  514. }
  515. else if (!(channelUserdata.Likes ?? true))
  516. {
  517. score -= 2;
  518. }
  519. if (channelUserdata.IsFavorite)
  520. {
  521. score += 3;
  522. }
  523. score += GetGenreScore(program.Genres, userId, genres);
  524. return score;
  525. }
  526. private int GetGenreScore(IEnumerable<string> programGenres, Guid userId, Dictionary<string, Genre> genres)
  527. {
  528. return programGenres.Select(i =>
  529. {
  530. var score = 0;
  531. Genre genre;
  532. if (genres.TryGetValue(i, out genre))
  533. {
  534. var genreUserdata = _userDataManager.GetUserData(userId, genre.GetUserDataKey());
  535. if ((genreUserdata.Likes ?? false))
  536. {
  537. score++;
  538. }
  539. else if (!(genreUserdata.Likes ?? true))
  540. {
  541. score--;
  542. }
  543. if (genreUserdata.IsFavorite)
  544. {
  545. score += 2;
  546. }
  547. }
  548. return score;
  549. }).Sum();
  550. }
  551. private async Task AddRecordingInfo(IEnumerable<ProgramInfoDto> programs, CancellationToken cancellationToken)
  552. {
  553. var timers = await ActiveService.GetTimersAsync(cancellationToken).ConfigureAwait(false);
  554. var timerList = timers.ToList();
  555. foreach (var program in programs)
  556. {
  557. var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, program.ExternalId, StringComparison.OrdinalIgnoreCase));
  558. if (timer != null)
  559. {
  560. program.TimerId = _tvDtoService.GetInternalTimerId(program.ServiceName, timer.Id)
  561. .ToString("N");
  562. if (!string.IsNullOrEmpty(timer.SeriesTimerId))
  563. {
  564. program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(program.ServiceName, timer.SeriesTimerId)
  565. .ToString("N");
  566. }
  567. }
  568. }
  569. }
  570. internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
  571. {
  572. await _refreshSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  573. try
  574. {
  575. await RefreshChannelsInternal(progress, cancellationToken).ConfigureAwait(false);
  576. }
  577. finally
  578. {
  579. _refreshSemaphore.Release();
  580. }
  581. }
  582. private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
  583. {
  584. // Avoid implicitly captured closure
  585. var service = ActiveService;
  586. if (service == null)
  587. {
  588. progress.Report(100);
  589. return;
  590. }
  591. progress.Report(10);
  592. var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false);
  593. var allChannelsList = allChannels.ToList();
  594. var list = new List<LiveTvChannel>();
  595. var numComplete = 0;
  596. foreach (var channelInfo in allChannelsList)
  597. {
  598. cancellationToken.ThrowIfCancellationRequested();
  599. try
  600. {
  601. var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false);
  602. list.Add(item);
  603. _libraryManager.RegisterItem(item);
  604. }
  605. catch (OperationCanceledException)
  606. {
  607. throw;
  608. }
  609. catch (Exception ex)
  610. {
  611. _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Item2.Name);
  612. }
  613. numComplete++;
  614. double percent = numComplete;
  615. percent /= allChannelsList.Count;
  616. progress.Report(5 * percent + 10);
  617. }
  618. _channelIdList = list.Select(i => i.Id).ToList();
  619. progress.Report(15);
  620. numComplete = 0;
  621. var programs = new List<LiveTvProgram>();
  622. var guideDays = GetGuideDays(list.Count);
  623. cancellationToken.ThrowIfCancellationRequested();
  624. foreach (var item in list)
  625. {
  626. cancellationToken.ThrowIfCancellationRequested();
  627. // Avoid implicitly captured closure
  628. var currentChannel = item;
  629. try
  630. {
  631. var start = DateTime.UtcNow.AddHours(-1);
  632. var end = start.AddDays(guideDays);
  633. var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false);
  634. var programEntities = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelType, service.Name, cancellationToken));
  635. programs.AddRange(programEntities);
  636. }
  637. catch (OperationCanceledException)
  638. {
  639. throw;
  640. }
  641. catch (Exception ex)
  642. {
  643. _logger.ErrorException("Error getting programs for channel {0}", ex, currentChannel.Name);
  644. }
  645. numComplete++;
  646. double percent = numComplete;
  647. percent /= allChannelsList.Count;
  648. progress.Report(80 * percent + 10);
  649. }
  650. _programs = programs.ToDictionary(i => i.Id);
  651. _refreshedPrograms.Clear();
  652. progress.Report(90);
  653. // Load these now which will prefetch metadata
  654. await GetRecordings(new RecordingQuery(), cancellationToken).ConfigureAwait(false);
  655. progress.Report(100);
  656. }
  657. public async Task CleanDatabase(IProgress<double> progress, CancellationToken cancellationToken)
  658. {
  659. var service = ActiveService;
  660. if (service == null)
  661. {
  662. progress.Report(100);
  663. return;
  664. }
  665. await _refreshSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  666. try
  667. {
  668. await DeleteOldPrograms(_programs.Keys.ToList(), progress, cancellationToken).ConfigureAwait(false);
  669. }
  670. finally
  671. {
  672. _refreshSemaphore.Release();
  673. }
  674. }
  675. private async Task DeleteOldPrograms(List<Guid> currentIdList, IProgress<double> progress, CancellationToken cancellationToken)
  676. {
  677. var list = _itemRepo.GetItemsOfType(typeof(LiveTvProgram)).ToList();
  678. var numComplete = 0;
  679. foreach (var program in list)
  680. {
  681. cancellationToken.ThrowIfCancellationRequested();
  682. if (!currentIdList.Contains(program.Id))
  683. {
  684. await _libraryManager.DeleteItem(program).ConfigureAwait(false);
  685. }
  686. numComplete++;
  687. double percent = numComplete;
  688. percent /= list.Count;
  689. progress.Report(100 * percent);
  690. }
  691. }
  692. private double GetGuideDays(int channelCount)
  693. {
  694. if (_config.Configuration.LiveTvOptions.GuideDays.HasValue)
  695. {
  696. return _config.Configuration.LiveTvOptions.GuideDays.Value;
  697. }
  698. var programsPerDay = channelCount * 48;
  699. const int maxPrograms = 24000;
  700. var days = Math.Round(((double)maxPrograms) / programsPerDay);
  701. // No less than 2, no more than 7
  702. return Math.Max(2, Math.Min(days, 7));
  703. }
  704. private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
  705. {
  706. var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
  707. return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
  708. }
  709. public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken)
  710. {
  711. var service = ActiveService;
  712. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
  713. var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
  714. if (!string.IsNullOrEmpty(query.ChannelId))
  715. {
  716. var guid = new Guid(query.ChannelId);
  717. var currentServiceName = service.Name;
  718. recordings = recordings
  719. .Where(i => _tvDtoService.GetInternalChannelId(currentServiceName, i.ChannelId) == guid);
  720. }
  721. if (!string.IsNullOrEmpty(query.Id))
  722. {
  723. var guid = new Guid(query.Id);
  724. var currentServiceName = service.Name;
  725. recordings = recordings
  726. .Where(i => _tvDtoService.GetInternalRecordingId(currentServiceName, i.Id) == guid);
  727. }
  728. if (!string.IsNullOrEmpty(query.GroupId))
  729. {
  730. var guid = new Guid(query.GroupId);
  731. recordings = recordings.Where(i => GetRecordingGroupIds(i).Contains(guid));
  732. }
  733. if (query.IsInProgress.HasValue)
  734. {
  735. var val = query.IsInProgress.Value;
  736. recordings = recordings.Where(i => (i.Status == RecordingStatus.InProgress) == val);
  737. }
  738. if (query.Status.HasValue)
  739. {
  740. var val = query.Status.Value;
  741. recordings = recordings.Where(i => (i.Status == val));
  742. }
  743. if (!string.IsNullOrEmpty(query.SeriesTimerId))
  744. {
  745. var guid = new Guid(query.SeriesTimerId);
  746. var currentServiceName = service.Name;
  747. recordings = recordings
  748. .Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid);
  749. }
  750. recordings = recordings.OrderByDescending(i => i.StartDate);
  751. IEnumerable<ILiveTvRecording> entities = await GetEntities(recordings, service.Name, cancellationToken).ConfigureAwait(false);
  752. if (user != null)
  753. {
  754. var currentUser = user;
  755. entities = entities.Where(i => i.IsParentalAllowed(currentUser));
  756. }
  757. var entityList = entities.ToList();
  758. entities = entityList;
  759. if (query.StartIndex.HasValue)
  760. {
  761. entities = entities.Skip(query.StartIndex.Value);
  762. }
  763. if (query.Limit.HasValue)
  764. {
  765. entities = entities.Take(query.Limit.Value);
  766. }
  767. var returnArray = entities
  768. .Select(i =>
  769. {
  770. var channel = string.IsNullOrEmpty(i.RecordingInfo.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.RecordingInfo.ChannelId));
  771. return _tvDtoService.GetRecordingInfoDto(i, channel, service, user);
  772. })
  773. .ToArray();
  774. return new QueryResult<RecordingInfoDto>
  775. {
  776. Items = returnArray,
  777. TotalRecordCount = entityList.Count
  778. };
  779. }
  780. private Task<ILiveTvRecording[]> GetEntities(IEnumerable<RecordingInfo> recordings, string serviceName, CancellationToken cancellationToken)
  781. {
  782. var tasks = recordings.Select(i => GetRecording(i, serviceName, cancellationToken));
  783. return Task.WhenAll(tasks);
  784. }
  785. private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId)
  786. {
  787. IEnumerable<ILiveTvService> services = _services;
  788. if (string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(channelId))
  789. {
  790. var channel = GetInternalChannel(channelId);
  791. if (channel != null)
  792. {
  793. serviceName = channel.ServiceName;
  794. }
  795. }
  796. if (!string.IsNullOrEmpty(serviceName))
  797. {
  798. services = services.Where(i => string.Equals(i.Name, serviceName, StringComparison.OrdinalIgnoreCase));
  799. }
  800. return services;
  801. }
  802. public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
  803. {
  804. var service = ActiveService;
  805. var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false);
  806. if (!string.IsNullOrEmpty(query.ChannelId))
  807. {
  808. var guid = new Guid(query.ChannelId);
  809. timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
  810. }
  811. if (!string.IsNullOrEmpty(query.SeriesTimerId))
  812. {
  813. var guid = new Guid(query.SeriesTimerId);
  814. var currentServiceName = service.Name;
  815. timers = timers
  816. .Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid);
  817. }
  818. var returnList = new List<TimerInfoDto>();
  819. foreach (var i in timers)
  820. {
  821. var program = string.IsNullOrEmpty(i.ProgramId) ?
  822. null :
  823. await GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"), cancellationToken).ConfigureAwait(false);
  824. var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
  825. returnList.Add(_tvDtoService.GetTimerInfoDto(i, service, program, channel));
  826. }
  827. var returnArray = returnList
  828. .OrderBy(i => i.StartDate)
  829. .ToArray();
  830. return new QueryResult<TimerInfoDto>
  831. {
  832. Items = returnArray,
  833. TotalRecordCount = returnArray.Length
  834. };
  835. }
  836. public async Task DeleteRecording(string recordingId)
  837. {
  838. var recording = await GetRecording(recordingId, CancellationToken.None).ConfigureAwait(false);
  839. if (recording == null)
  840. {
  841. throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
  842. }
  843. var service = GetServices(recording.ServiceName, null)
  844. .First();
  845. await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
  846. }
  847. public async Task CancelTimer(string id)
  848. {
  849. var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
  850. if (timer == null)
  851. {
  852. throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
  853. }
  854. var service = GetServices(timer.ServiceName, null)
  855. .First();
  856. await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
  857. }
  858. public async Task CancelSeriesTimer(string id)
  859. {
  860. var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
  861. if (timer == null)
  862. {
  863. throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
  864. }
  865. var service = GetServices(timer.ServiceName, null)
  866. .First();
  867. await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
  868. }
  869. public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken, User user = null)
  870. {
  871. var results = await GetRecordings(new RecordingQuery
  872. {
  873. UserId = user == null ? null : user.Id.ToString("N"),
  874. Id = id
  875. }, cancellationToken).ConfigureAwait(false);
  876. return results.Items.FirstOrDefault();
  877. }
  878. public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
  879. {
  880. var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false);
  881. return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
  882. }
  883. public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
  884. {
  885. var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
  886. return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
  887. }
  888. public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
  889. {
  890. var service = ActiveService;
  891. var timers = await service.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
  892. if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
  893. {
  894. timers = query.SortOrder == SortOrder.Descending ?
  895. timers.OrderBy(i => i.Priority).ThenByStringDescending(i => i.Name) :
  896. timers.OrderByDescending(i => i.Priority).ThenByString(i => i.Name);
  897. }
  898. else
  899. {
  900. timers = query.SortOrder == SortOrder.Descending ?
  901. timers.OrderByStringDescending(i => i.Name) :
  902. timers.OrderByString(i => i.Name);
  903. }
  904. var returnArray = timers
  905. .Select(i =>
  906. {
  907. string channelName = null;
  908. if (!string.IsNullOrEmpty(i.ChannelId))
  909. {
  910. var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId);
  911. var channel = GetInternalChannel(internalChannelId);
  912. channelName = channel == null ? null : channel.Name;
  913. }
  914. return _tvDtoService.GetSeriesTimerInfoDto(i, service, channelName);
  915. })
  916. .ToArray();
  917. return new QueryResult<SeriesTimerInfoDto>
  918. {
  919. Items = returnArray,
  920. TotalRecordCount = returnArray.Length
  921. };
  922. }
  923. public async Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null)
  924. {
  925. var channel = GetInternalChannel(id);
  926. var currentProgram = await GetCurrentProgram(channel.ExternalId, cancellationToken).ConfigureAwait(false);
  927. var dto = _tvDtoService.GetChannelInfoDto(channel, currentProgram, user);
  928. return dto;
  929. }
  930. private async Task<LiveTvProgram> GetCurrentProgram(string externalChannelId, CancellationToken cancellationToken)
  931. {
  932. var now = DateTime.UtcNow;
  933. var program = _programs.Values
  934. .Where(i => string.Equals(externalChannelId, i.ExternalChannelId, StringComparison.OrdinalIgnoreCase))
  935. .OrderBy(i => i.StartDate)
  936. .SkipWhile(i => now >= (i.EndDate ?? DateTime.MinValue))
  937. .FirstOrDefault();
  938. if (program != null)
  939. {
  940. await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false);
  941. }
  942. return program;
  943. }
  944. private async Task<SeriesTimerInfo> GetNewTimerDefaultsInternal(CancellationToken cancellationToken, LiveTvProgram program = null)
  945. {
  946. ProgramInfo programInfo = null;
  947. if (program != null)
  948. {
  949. programInfo = new ProgramInfo
  950. {
  951. Audio = program.Audio,
  952. ChannelId = program.ExternalChannelId,
  953. CommunityRating = program.CommunityRating,
  954. EndDate = program.EndDate ?? DateTime.MinValue,
  955. EpisodeTitle = program.EpisodeTitle,
  956. Genres = program.Genres,
  957. HasImage = program.HasProviderImage,
  958. Id = program.ExternalId,
  959. IsHD = program.IsHD,
  960. IsKids = program.IsKids,
  961. IsLive = program.IsLive,
  962. IsMovie = program.IsMovie,
  963. IsNews = program.IsNews,
  964. IsPremiere = program.IsPremiere,
  965. IsRepeat = program.IsRepeat,
  966. IsSeries = program.IsSeries,
  967. IsSports = program.IsSports,
  968. OriginalAirDate = program.PremiereDate,
  969. Overview = program.Overview,
  970. StartDate = program.StartDate,
  971. ImagePath = program.ProviderImagePath,
  972. ImageUrl = program.ProviderImageUrl,
  973. Name = program.Name,
  974. OfficialRating = program.OfficialRating
  975. };
  976. }
  977. var info = await ActiveService.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
  978. info.Id = null;
  979. return info;
  980. }
  981. public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
  982. {
  983. var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
  984. var obj = _tvDtoService.GetSeriesTimerInfoDto(info, ActiveService, null);
  985. return obj;
  986. }
  987. public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
  988. {
  989. var program = await GetInternalProgram(programId, cancellationToken).ConfigureAwait(false);
  990. var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
  991. var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
  992. var info = _tvDtoService.GetSeriesTimerInfoDto(defaults, ActiveService, null);
  993. info.Days = new List<DayOfWeek>
  994. {
  995. program.StartDate.ToLocalTime().DayOfWeek
  996. };
  997. info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
  998. info.Name = program.Name;
  999. info.ChannelId = programDto.ChannelId;
  1000. info.ChannelName = programDto.ChannelName;
  1001. info.StartDate = program.StartDate;
  1002. info.Name = program.Name;
  1003. info.Overview = program.Overview;
  1004. info.ProgramId = programDto.Id;
  1005. info.ExternalProgramId = programDto.ExternalId;
  1006. if (program.EndDate.HasValue)
  1007. {
  1008. info.EndDate = program.EndDate.Value;
  1009. }
  1010. return info;
  1011. }
  1012. public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
  1013. {
  1014. var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
  1015. var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
  1016. // Set priority from default values
  1017. var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  1018. info.Priority = defaultValues.Priority;
  1019. await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1020. }
  1021. public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
  1022. {
  1023. var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
  1024. var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
  1025. // Set priority from default values
  1026. var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  1027. info.Priority = defaultValues.Priority;
  1028. await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1029. }
  1030. public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
  1031. {
  1032. var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
  1033. var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
  1034. await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1035. }
  1036. public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
  1037. {
  1038. var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
  1039. var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
  1040. await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1041. }
  1042. private IEnumerable<string> GetRecordingGroupNames(RecordingInfo recording)
  1043. {
  1044. var list = new List<string>();
  1045. if (recording.IsSeries)
  1046. {
  1047. list.Add(recording.Name);
  1048. }
  1049. if (recording.IsKids)
  1050. {
  1051. list.Add("Kids");
  1052. }
  1053. if (recording.IsMovie)
  1054. {
  1055. list.Add("Movies");
  1056. }
  1057. if (recording.IsNews)
  1058. {
  1059. list.Add("News");
  1060. }
  1061. if (recording.IsSports)
  1062. {
  1063. list.Add("Sports");
  1064. }
  1065. if (!recording.IsSports && !recording.IsNews && !recording.IsMovie && !recording.IsKids && !recording.IsSeries)
  1066. {
  1067. list.Add("Others");
  1068. }
  1069. return list;
  1070. }
  1071. private List<Guid> GetRecordingGroupIds(RecordingInfo recording)
  1072. {
  1073. return GetRecordingGroupNames(recording).Select(i => i.ToLower()
  1074. .GetMD5())
  1075. .ToList();
  1076. }
  1077. public async Task<QueryResult<RecordingGroupDto>> GetRecordingGroups(RecordingGroupQuery query, CancellationToken cancellationToken)
  1078. {
  1079. var recordingResult = await GetRecordings(new RecordingQuery
  1080. {
  1081. UserId = query.UserId
  1082. }, cancellationToken).ConfigureAwait(false);
  1083. var recordings = recordingResult.Items;
  1084. var groups = new List<RecordingGroupDto>();
  1085. var series = recordings
  1086. .Where(i => i.IsSeries)
  1087. .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
  1088. .ToList();
  1089. groups.AddRange(series.OrderByString(i => i.Key).Select(i => new RecordingGroupDto
  1090. {
  1091. Name = i.Key,
  1092. RecordingCount = i.Count()
  1093. }));
  1094. groups.Add(new RecordingGroupDto
  1095. {
  1096. Name = "Kids",
  1097. RecordingCount = recordings.Count(i => i.IsKids)
  1098. });
  1099. groups.Add(new RecordingGroupDto
  1100. {
  1101. Name = "Movies",
  1102. RecordingCount = recordings.Count(i => i.IsMovie)
  1103. });
  1104. groups.Add(new RecordingGroupDto
  1105. {
  1106. Name = "News",
  1107. RecordingCount = recordings.Count(i => i.IsNews)
  1108. });
  1109. groups.Add(new RecordingGroupDto
  1110. {
  1111. Name = "Sports",
  1112. RecordingCount = recordings.Count(i => i.IsSports)
  1113. });
  1114. groups.Add(new RecordingGroupDto
  1115. {
  1116. Name = "Others",
  1117. RecordingCount = recordings.Count(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries)
  1118. });
  1119. groups = groups
  1120. .Where(i => i.RecordingCount > 0)
  1121. .ToList();
  1122. foreach (var group in groups)
  1123. {
  1124. group.Id = group.Name.ToLower().GetMD5().ToString("N");
  1125. }
  1126. return new QueryResult<RecordingGroupDto>
  1127. {
  1128. Items = groups.ToArray(),
  1129. TotalRecordCount = groups.Count
  1130. };
  1131. }
  1132. public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
  1133. {
  1134. await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  1135. var service = ActiveService;
  1136. _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
  1137. try
  1138. {
  1139. await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false);
  1140. }
  1141. catch (Exception ex)
  1142. {
  1143. _logger.ErrorException("Error closing live stream", ex);
  1144. throw;
  1145. }
  1146. finally
  1147. {
  1148. _liveStreamSemaphore.Release();
  1149. }
  1150. }
  1151. public GuideInfo GetGuideInfo()
  1152. {
  1153. var programs = _programs.ToList();
  1154. var startDate = _programs.Count == 0 ? DateTime.MinValue :
  1155. programs.Select(i => i.Value.StartDate).Min();
  1156. var endDate = programs.Count == 0 ? DateTime.MinValue :
  1157. programs.Select(i => i.Value.StartDate).Max();
  1158. return new GuideInfo
  1159. {
  1160. StartDate = startDate,
  1161. EndDate = endDate
  1162. };
  1163. }
  1164. /// <summary>
  1165. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  1166. /// </summary>
  1167. public void Dispose()
  1168. {
  1169. Dispose(true);
  1170. }
  1171. private readonly object _disposeLock = new object();
  1172. /// <summary>
  1173. /// Releases unmanaged and - optionally - managed resources.
  1174. /// </summary>
  1175. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  1176. protected virtual void Dispose(bool dispose)
  1177. {
  1178. if (dispose)
  1179. {
  1180. lock (_disposeLock)
  1181. {
  1182. foreach (var stream in _openStreams.Values.ToList())
  1183. {
  1184. var task = CloseLiveStream(stream.Id, CancellationToken.None);
  1185. Task.WaitAll(task);
  1186. }
  1187. _openStreams.Clear();
  1188. }
  1189. }
  1190. }
  1191. private async Task<IEnumerable<LiveTvServiceInfo>> GetServiceInfos(CancellationToken cancellationToken)
  1192. {
  1193. var tasks = Services.Select(i => GetServiceInfo(i, cancellationToken));
  1194. return await Task.WhenAll(tasks).ConfigureAwait(false);
  1195. }
  1196. private async Task<LiveTvServiceInfo> GetServiceInfo(ILiveTvService service, CancellationToken cancellationToken)
  1197. {
  1198. var info = new LiveTvServiceInfo
  1199. {
  1200. Name = service.Name
  1201. };
  1202. try
  1203. {
  1204. var statusInfo = await service.GetStatusInfoAsync(cancellationToken).ConfigureAwait(false);
  1205. info.Status = statusInfo.Status;
  1206. info.StatusMessage = statusInfo.StatusMessage;
  1207. info.Version = statusInfo.Version;
  1208. info.HasUpdateAvailable = statusInfo.HasUpdateAvailable;
  1209. info.HomePageUrl = service.HomePageUrl;
  1210. info.Tuners = statusInfo.Tuners.Select(i =>
  1211. {
  1212. string channelName = null;
  1213. if (!string.IsNullOrEmpty(i.ChannelId))
  1214. {
  1215. var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId);
  1216. var channel = GetInternalChannel(internalChannelId);
  1217. channelName = channel == null ? null : channel.Name;
  1218. }
  1219. return _tvDtoService.GetTunerInfoDto(service.Name, i, channelName);
  1220. }).ToList();
  1221. }
  1222. catch (Exception ex)
  1223. {
  1224. _logger.ErrorException("Error getting service status info from {0}", ex, service.Name);
  1225. info.Status = LiveTvServiceStatus.Unavailable;
  1226. info.StatusMessage = ex.Message;
  1227. }
  1228. return info;
  1229. }
  1230. public async Task<LiveTvInfo> GetLiveTvInfo(CancellationToken cancellationToken)
  1231. {
  1232. var services = await GetServiceInfos(CancellationToken.None).ConfigureAwait(false);
  1233. var servicesList = services.ToList();
  1234. var activeServiceInfo = ActiveService == null ? null :
  1235. servicesList.FirstOrDefault(i => string.Equals(i.Name, ActiveService.Name, StringComparison.OrdinalIgnoreCase));
  1236. var info = new LiveTvInfo
  1237. {
  1238. Services = servicesList.ToList(),
  1239. ActiveServiceName = activeServiceInfo == null ? null : activeServiceInfo.Name,
  1240. IsEnabled = ActiveService != null,
  1241. Status = activeServiceInfo == null ? LiveTvServiceStatus.Unavailable : activeServiceInfo.Status,
  1242. StatusMessage = activeServiceInfo == null ? null : activeServiceInfo.StatusMessage
  1243. };
  1244. info.EnabledUsers = _userManager.Users
  1245. .Where(i => i.Configuration.EnableLiveTvAccess && info.IsEnabled)
  1246. .Select(i => i.Id.ToString("N"))
  1247. .ToList();
  1248. return info;
  1249. }
  1250. /// <summary>
  1251. /// Resets the tuner.
  1252. /// </summary>
  1253. /// <param name="id">The identifier.</param>
  1254. /// <param name="cancellationToken">The cancellation token.</param>
  1255. /// <returns>Task.</returns>
  1256. public Task ResetTuner(string id, CancellationToken cancellationToken)
  1257. {
  1258. return ActiveService.ResetTuner(id, cancellationToken);
  1259. }
  1260. }
  1261. }