EmbyTV.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. using MediaBrowser.Common;
  2. using MediaBrowser.Common.Configuration;
  3. using MediaBrowser.Common.IO;
  4. using MediaBrowser.Common.Net;
  5. using MediaBrowser.Common.Security;
  6. using MediaBrowser.Controller.Drawing;
  7. using MediaBrowser.Controller.LiveTv;
  8. using MediaBrowser.Model.Dto;
  9. using MediaBrowser.Model.Entities;
  10. using MediaBrowser.Model.Events;
  11. using MediaBrowser.Model.LiveTv;
  12. using MediaBrowser.Model.Logging;
  13. using MediaBrowser.Model.Serialization;
  14. using System;
  15. using System.Collections.Concurrent;
  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.LiveTv.EmbyTV
  22. {
  23. public class EmbyTV : ILiveTvService/*, IHasRegistrationInfo*/, IDisposable
  24. {
  25. private readonly IApplicationHost _appHpst;
  26. private readonly ILogger _logger;
  27. private readonly IHttpClient _httpClient;
  28. private readonly IConfigurationManager _config;
  29. private readonly IJsonSerializer _jsonSerializer;
  30. private readonly ItemDataProvider<RecordingInfo> _recordingProvider;
  31. private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider;
  32. private readonly TimerManager _timerProvider;
  33. private readonly LiveTvManager _liveTvManager;
  34. private readonly IFileSystem _fileSystem;
  35. private readonly ISecurityManager _security;
  36. public static EmbyTV Current;
  37. public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security)
  38. {
  39. Current = this;
  40. _appHpst = appHost;
  41. _logger = logger;
  42. _httpClient = httpClient;
  43. _config = config;
  44. _fileSystem = fileSystem;
  45. _security = security;
  46. _liveTvManager = (LiveTvManager)liveTvManager;
  47. _jsonSerializer = jsonSerializer;
  48. _recordingProvider = new ItemDataProvider<RecordingInfo>(jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase));
  49. _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
  50. _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers"));
  51. _timerProvider.TimerFired += _timerProvider_TimerFired;
  52. }
  53. public void Start()
  54. {
  55. _timerProvider.RestartTimers();
  56. }
  57. public event EventHandler DataSourceChanged;
  58. public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged;
  59. private readonly ConcurrentDictionary<string, CancellationTokenSource> _activeRecordings =
  60. new ConcurrentDictionary<string, CancellationTokenSource>(StringComparer.OrdinalIgnoreCase);
  61. public string Name
  62. {
  63. get { return "Emby"; }
  64. }
  65. public string DataPath
  66. {
  67. get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv"); }
  68. }
  69. public string HomePageUrl
  70. {
  71. get { return "http://emby.media"; }
  72. }
  73. public async Task<LiveTvServiceStatusInfo> GetStatusInfoAsync(CancellationToken cancellationToken)
  74. {
  75. var status = new LiveTvServiceStatusInfo();
  76. var list = new List<LiveTvTunerInfo>();
  77. foreach (var hostInstance in _liveTvManager.TunerHosts)
  78. {
  79. try
  80. {
  81. var tuners = await hostInstance.GetTunerInfos(cancellationToken).ConfigureAwait(false);
  82. list.AddRange(tuners);
  83. }
  84. catch (Exception ex)
  85. {
  86. _logger.ErrorException("Error getting tuners", ex);
  87. }
  88. }
  89. status.Tuners = list;
  90. status.Status = LiveTvServiceStatus.Ok;
  91. status.Version = _appHpst.ApplicationVersion.ToString();
  92. status.IsVisible = false;
  93. return status;
  94. }
  95. private List<ChannelInfo> _channelCache = null;
  96. private async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken)
  97. {
  98. if (enableCache && _channelCache != null)
  99. {
  100. return _channelCache.ToList();
  101. }
  102. var list = new List<ChannelInfo>();
  103. foreach (var hostInstance in _liveTvManager.TunerHosts)
  104. {
  105. try
  106. {
  107. var channels = await hostInstance.GetChannels(cancellationToken).ConfigureAwait(false);
  108. list.AddRange(channels);
  109. }
  110. catch (Exception ex)
  111. {
  112. _logger.ErrorException("Error getting channels", ex);
  113. }
  114. }
  115. if (list.Count > 0)
  116. {
  117. foreach (var provider in GetListingProviders())
  118. {
  119. try
  120. {
  121. await provider.Item1.AddMetadata(provider.Item2, list, cancellationToken).ConfigureAwait(false);
  122. }
  123. catch (NotSupportedException)
  124. {
  125. }
  126. catch (Exception ex)
  127. {
  128. _logger.ErrorException("Error adding metadata", ex);
  129. }
  130. }
  131. }
  132. _channelCache = list;
  133. return list;
  134. }
  135. public Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken)
  136. {
  137. return GetChannelsAsync(false, cancellationToken);
  138. }
  139. public Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken)
  140. {
  141. var timers = _timerProvider.GetAll().Where(i => string.Equals(i.SeriesTimerId, timerId, StringComparison.OrdinalIgnoreCase));
  142. foreach (var timer in timers)
  143. {
  144. CancelTimerInternal(timer.Id);
  145. }
  146. var remove = _seriesTimerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
  147. if (remove != null)
  148. {
  149. _seriesTimerProvider.Delete(remove);
  150. }
  151. return Task.FromResult(true);
  152. }
  153. private void CancelTimerInternal(string timerId)
  154. {
  155. var remove = _timerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
  156. if (remove != null)
  157. {
  158. _timerProvider.Delete(remove);
  159. }
  160. CancellationTokenSource cancellationTokenSource;
  161. if (_activeRecordings.TryGetValue(timerId, out cancellationTokenSource))
  162. {
  163. cancellationTokenSource.Cancel();
  164. }
  165. }
  166. public Task CancelTimerAsync(string timerId, CancellationToken cancellationToken)
  167. {
  168. CancelTimerInternal(timerId);
  169. return Task.FromResult(true);
  170. }
  171. public async Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
  172. {
  173. var remove = _recordingProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, recordingId, StringComparison.OrdinalIgnoreCase));
  174. if (remove != null)
  175. {
  176. if (!string.IsNullOrWhiteSpace(remove.TimerId))
  177. {
  178. var enableDelay = _activeRecordings.ContainsKey(remove.TimerId);
  179. CancelTimerInternal(remove.TimerId);
  180. if (enableDelay)
  181. {
  182. // A hack yes, but need to make sure the file is closed before attempting to delete it
  183. await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
  184. }
  185. }
  186. try
  187. {
  188. File.Delete(remove.Path);
  189. }
  190. catch (DirectoryNotFoundException)
  191. {
  192. }
  193. catch (FileNotFoundException)
  194. {
  195. }
  196. _recordingProvider.Delete(remove);
  197. }
  198. }
  199. public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
  200. {
  201. info.Id = Guid.NewGuid().ToString("N");
  202. _timerProvider.Add(info);
  203. return Task.FromResult(0);
  204. }
  205. public async Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
  206. {
  207. info.Id = Guid.NewGuid().ToString("N");
  208. List<ProgramInfo> epgData;
  209. if (info.RecordAnyChannel)
  210. {
  211. var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
  212. var channelIds = channels.Select(i => i.Id).ToList();
  213. epgData = GetEpgDataForChannels(channelIds);
  214. }
  215. else
  216. {
  217. epgData = GetEpgDataForChannel(info.ChannelId);
  218. }
  219. // populate info.seriesID
  220. var program = epgData.FirstOrDefault(i => string.Equals(i.Id, info.ProgramId, StringComparison.OrdinalIgnoreCase));
  221. if (program != null)
  222. {
  223. info.SeriesId = program.SeriesId;
  224. }
  225. else
  226. {
  227. throw new InvalidOperationException("SeriesId for program not found");
  228. }
  229. _seriesTimerProvider.Add(info);
  230. await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false);
  231. }
  232. public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
  233. {
  234. _seriesTimerProvider.Update(info);
  235. List<ProgramInfo> epgData;
  236. if (info.RecordAnyChannel)
  237. {
  238. var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
  239. var channelIds = channels.Select(i => i.Id).ToList();
  240. epgData = GetEpgDataForChannels(channelIds);
  241. }
  242. else
  243. {
  244. epgData = GetEpgDataForChannel(info.ChannelId);
  245. }
  246. await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false);
  247. }
  248. public Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
  249. {
  250. _timerProvider.Update(info);
  251. return Task.FromResult(true);
  252. }
  253. public Task<ImageStream> GetChannelImageAsync(string channelId, CancellationToken cancellationToken)
  254. {
  255. throw new NotImplementedException();
  256. }
  257. public Task<ImageStream> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken)
  258. {
  259. throw new NotImplementedException();
  260. }
  261. public Task<ImageStream> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken)
  262. {
  263. throw new NotImplementedException();
  264. }
  265. public Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
  266. {
  267. return Task.FromResult((IEnumerable<RecordingInfo>)_recordingProvider.GetAll());
  268. }
  269. public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
  270. {
  271. return Task.FromResult((IEnumerable<TimerInfo>)_timerProvider.GetAll());
  272. }
  273. public Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null)
  274. {
  275. var defaults = new SeriesTimerInfo()
  276. {
  277. PostPaddingSeconds = 0,
  278. PrePaddingSeconds = 0,
  279. RecordAnyChannel = false,
  280. RecordAnyTime = false,
  281. RecordNewOnly = false
  282. };
  283. if (program != null)
  284. {
  285. defaults.SeriesId = program.SeriesId;
  286. defaults.ProgramId = program.Id;
  287. }
  288. return Task.FromResult(defaults);
  289. }
  290. public Task<IEnumerable<SeriesTimerInfo>> GetSeriesTimersAsync(CancellationToken cancellationToken)
  291. {
  292. return Task.FromResult((IEnumerable<SeriesTimerInfo>)_seriesTimerProvider.GetAll());
  293. }
  294. public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
  295. {
  296. var channels = await GetChannelsAsync(true, cancellationToken).ConfigureAwait(false);
  297. var channel = channels.First(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
  298. foreach (var provider in GetListingProviders())
  299. {
  300. var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channel.Number, startDateUtc, endDateUtc, cancellationToken)
  301. .ConfigureAwait(false);
  302. var list = programs.ToList();
  303. // Replace the value that came from the provider with a normalized value
  304. foreach (var program in list)
  305. {
  306. program.ChannelId = channelId;
  307. }
  308. if (list.Count > 0)
  309. {
  310. SaveEpgDataForChannel(channelId, list);
  311. return list;
  312. }
  313. }
  314. return new List<ProgramInfo>();
  315. }
  316. private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
  317. {
  318. return GetConfiguration().ListingProviders
  319. .Select(i =>
  320. {
  321. var provider = _liveTvManager.ListingProviders.FirstOrDefault(l => string.Equals(l.Type, i.Type, StringComparison.OrdinalIgnoreCase));
  322. return provider == null ? null : new Tuple<IListingsProvider, ListingsProviderInfo>(provider, i);
  323. })
  324. .Where(i => i != null)
  325. .ToList();
  326. }
  327. public Task<MediaSourceInfo> GetRecordingStream(string recordingId, string streamId, CancellationToken cancellationToken)
  328. {
  329. throw new NotImplementedException();
  330. }
  331. public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
  332. {
  333. _logger.Info("Streaming Channel " + channelId);
  334. foreach (var hostInstance in _liveTvManager.TunerHosts)
  335. {
  336. MediaSourceInfo mediaSourceInfo = null;
  337. try
  338. {
  339. mediaSourceInfo = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
  340. }
  341. catch (Exception e)
  342. {
  343. _logger.ErrorException("Error getting channel stream", e);
  344. }
  345. if (mediaSourceInfo != null)
  346. {
  347. mediaSourceInfo.Id = Guid.NewGuid().ToString("N");
  348. return mediaSourceInfo;
  349. }
  350. }
  351. throw new ApplicationException("Tuner not found.");
  352. }
  353. public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
  354. {
  355. foreach (var hostInstance in _liveTvManager.TunerHosts)
  356. {
  357. try
  358. {
  359. var sources = await hostInstance.GetChannelStreamMediaSources(channelId, cancellationToken).ConfigureAwait(false);
  360. if (sources.Count > 0)
  361. {
  362. return sources;
  363. }
  364. }
  365. catch (NotImplementedException)
  366. {
  367. }
  368. }
  369. throw new ApplicationException("Tuner not found.");
  370. }
  371. public Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(string recordingId, CancellationToken cancellationToken)
  372. {
  373. throw new NotImplementedException();
  374. }
  375. public Task CloseLiveStream(string id, CancellationToken cancellationToken)
  376. {
  377. return Task.FromResult(0);
  378. }
  379. public Task RecordLiveStream(string id, CancellationToken cancellationToken)
  380. {
  381. return Task.FromResult(0);
  382. }
  383. public Task ResetTuner(string id, CancellationToken cancellationToken)
  384. {
  385. return Task.FromResult(0);
  386. }
  387. async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e)
  388. {
  389. var timer = e.Argument;
  390. try
  391. {
  392. var cancellationTokenSource = new CancellationTokenSource();
  393. if (_activeRecordings.TryAdd(timer.Id, cancellationTokenSource))
  394. {
  395. await RecordStream(timer, cancellationTokenSource.Token).ConfigureAwait(false);
  396. }
  397. }
  398. catch (OperationCanceledException)
  399. {
  400. }
  401. catch (Exception ex)
  402. {
  403. _logger.ErrorException("Error recording stream", ex);
  404. if (DateTime.UtcNow < timer.EndDate)
  405. {
  406. const int retryIntervalSeconds = 60;
  407. _logger.Debug("Retrying recording in {0} seconds.", retryIntervalSeconds);
  408. _timerProvider.StartTimer(timer, TimeSpan.FromSeconds(retryIntervalSeconds));
  409. }
  410. }
  411. }
  412. private async Task RecordStream(TimerInfo timer, CancellationToken cancellationToken)
  413. {
  414. if (timer == null)
  415. {
  416. throw new ArgumentNullException("timer");
  417. }
  418. var mediaStreamInfo = await GetChannelStream(timer.ChannelId, null, CancellationToken.None);
  419. var duration = (timer.EndDate - DateTime.UtcNow).Add(TimeSpan.FromSeconds(timer.PostPaddingSeconds));
  420. HttpRequestOptions httpRequestOptions = new HttpRequestOptions()
  421. {
  422. Url = mediaStreamInfo.Path
  423. };
  424. var info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
  425. var recordPath = RecordingPath;
  426. if (info.IsMovie)
  427. {
  428. recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name));
  429. }
  430. else if (info.IsSeries)
  431. {
  432. recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name));
  433. }
  434. else if (info.IsKids)
  435. {
  436. recordPath = Path.Combine(recordPath, "Kids", _fileSystem.GetValidFilename(info.Name));
  437. }
  438. else if (info.IsSports)
  439. {
  440. recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name));
  441. }
  442. else
  443. {
  444. recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name));
  445. }
  446. var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)) + ".ts";
  447. recordPath = Path.Combine(recordPath, recordingFileName);
  448. Directory.CreateDirectory(Path.GetDirectoryName(recordPath));
  449. var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.ProgramId, info.Id, StringComparison.OrdinalIgnoreCase));
  450. if (recording == null)
  451. {
  452. recording = new RecordingInfo
  453. {
  454. ChannelId = info.ChannelId,
  455. Id = Guid.NewGuid().ToString("N"),
  456. StartDate = info.StartDate,
  457. EndDate = info.EndDate,
  458. Genres = info.Genres,
  459. IsKids = info.IsKids,
  460. IsLive = info.IsLive,
  461. IsMovie = info.IsMovie,
  462. IsHD = info.IsHD,
  463. IsNews = info.IsNews,
  464. IsPremiere = info.IsPremiere,
  465. IsSeries = info.IsSeries,
  466. IsSports = info.IsSports,
  467. IsRepeat = !info.IsPremiere,
  468. Name = info.Name,
  469. EpisodeTitle = info.EpisodeTitle,
  470. ProgramId = info.Id,
  471. HasImage = info.HasImage,
  472. ImagePath = info.ImagePath,
  473. ImageUrl = info.ImageUrl,
  474. OriginalAirDate = info.OriginalAirDate,
  475. Status = RecordingStatus.Scheduled,
  476. Overview = info.Overview,
  477. SeriesTimerId = timer.SeriesTimerId,
  478. TimerId = timer.Id,
  479. ShowId = info.ShowId
  480. };
  481. _recordingProvider.Add(recording);
  482. }
  483. recording.Path = recordPath;
  484. recording.Status = RecordingStatus.InProgress;
  485. recording.DateLastUpdated = DateTime.UtcNow;
  486. _recordingProvider.Update(recording);
  487. try
  488. {
  489. httpRequestOptions.BufferContent = false;
  490. var durationToken = new CancellationTokenSource(duration);
  491. var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
  492. httpRequestOptions.CancellationToken = linkedToken;
  493. _logger.Info("Writing file to path: " + recordPath);
  494. using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET"))
  495. {
  496. using (var output = File.Open(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read))
  497. {
  498. await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken);
  499. }
  500. }
  501. recording.Status = RecordingStatus.Completed;
  502. _logger.Info("Recording completed");
  503. }
  504. catch (OperationCanceledException)
  505. {
  506. _logger.Info("Recording stopped");
  507. recording.Status = RecordingStatus.Completed;
  508. }
  509. catch (Exception ex)
  510. {
  511. _logger.ErrorException("Error recording", ex);
  512. recording.Status = RecordingStatus.Error;
  513. }
  514. recording.DateLastUpdated = DateTime.UtcNow;
  515. _recordingProvider.Update(recording);
  516. _timerProvider.Delete(timer);
  517. _logger.Info("Recording was a success");
  518. }
  519. private ProgramInfo GetProgramInfoFromCache(string channelId, string programId)
  520. {
  521. var epgData = GetEpgDataForChannel(channelId);
  522. return epgData.FirstOrDefault(p => string.Equals(p.Id, programId, StringComparison.OrdinalIgnoreCase));
  523. }
  524. private string RecordingPath
  525. {
  526. get
  527. {
  528. var path = GetConfiguration().RecordingPath;
  529. return string.IsNullOrWhiteSpace(path)
  530. ? Path.Combine(DataPath, "recordings")
  531. : path;
  532. }
  533. }
  534. private LiveTvOptions GetConfiguration()
  535. {
  536. return _config.GetConfiguration<LiveTvOptions>("livetv");
  537. }
  538. private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer)
  539. {
  540. var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
  541. if (registration.IsValid)
  542. {
  543. var newTimers = GetTimersForSeries(seriesTimer, epgData, _recordingProvider.GetAll()).ToList();
  544. foreach (var timer in newTimers)
  545. {
  546. _timerProvider.AddOrUpdate(timer);
  547. }
  548. }
  549. }
  550. private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms, IReadOnlyList<RecordingInfo> currentRecordings)
  551. {
  552. // Exclude programs that have already ended
  553. allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow);
  554. allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
  555. var recordingShowIds = currentRecordings.Select(i => i.ProgramId).Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
  556. allPrograms = allPrograms.Where(i => !recordingShowIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase));
  557. return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer));
  558. }
  559. private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
  560. {
  561. if (!seriesTimer.RecordAnyTime)
  562. {
  563. allPrograms = allPrograms.Where(epg => (seriesTimer.StartDate.TimeOfDay == epg.StartDate.TimeOfDay));
  564. }
  565. if (seriesTimer.RecordNewOnly)
  566. {
  567. allPrograms = allPrograms.Where(epg => !epg.IsRepeat);
  568. }
  569. if (!seriesTimer.RecordAnyChannel)
  570. {
  571. allPrograms = allPrograms.Where(epg => string.Equals(epg.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase));
  572. }
  573. allPrograms = allPrograms.Where(i => seriesTimer.Days.Contains(i.StartDate.ToLocalTime().DayOfWeek));
  574. if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
  575. {
  576. _logger.Error("seriesTimer.SeriesId is null. Cannot find programs for series");
  577. return new List<ProgramInfo>();
  578. }
  579. return allPrograms.Where(i => string.Equals(i.SeriesId, seriesTimer.SeriesId, StringComparison.OrdinalIgnoreCase));
  580. }
  581. private string GetChannelEpgCachePath(string channelId)
  582. {
  583. return Path.Combine(DataPath, "epg", channelId + ".json");
  584. }
  585. private readonly object _epgLock = new object();
  586. private void SaveEpgDataForChannel(string channelId, List<ProgramInfo> epgData)
  587. {
  588. var path = GetChannelEpgCachePath(channelId);
  589. Directory.CreateDirectory(Path.GetDirectoryName(path));
  590. lock (_epgLock)
  591. {
  592. _jsonSerializer.SerializeToFile(epgData, path);
  593. }
  594. }
  595. private List<ProgramInfo> GetEpgDataForChannel(string channelId)
  596. {
  597. try
  598. {
  599. lock (_epgLock)
  600. {
  601. return _jsonSerializer.DeserializeFromFile<List<ProgramInfo>>(GetChannelEpgCachePath(channelId));
  602. }
  603. }
  604. catch
  605. {
  606. return new List<ProgramInfo>();
  607. }
  608. }
  609. private List<ProgramInfo> GetEpgDataForChannels(List<string> channelIds)
  610. {
  611. return channelIds.SelectMany(GetEpgDataForChannel).ToList();
  612. }
  613. public void Dispose()
  614. {
  615. foreach (var pair in _activeRecordings.ToList())
  616. {
  617. pair.Value.Cancel();
  618. }
  619. }
  620. public Task<MBRegistrationRecord> GetRegistrationInfo(string feature)
  621. {
  622. if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase))
  623. {
  624. return _security.GetRegistrationStatus("embytvseriesrecordings");
  625. }
  626. return Task.FromResult(new MBRegistrationRecord
  627. {
  628. IsValid = true,
  629. IsRegistered = true
  630. });
  631. }
  632. }
  633. }